大连凯杰建设有限公司网站,免费企业信息黄页网,怎么利用网站开发app,搜索引擎营销的方法有哪些若依作为最近非常火的脚手架#xff0c;分析它的源码#xff0c;不仅可以更好的使用它#xff0c;在出错时及时定位#xff0c;也可以在需要个性化功能时轻车熟路的修改它以满足我们自己的需求#xff0c;同时也可以学习人家解决问题的思路#xff0c;提升自己的技术水平…若依作为最近非常火的脚手架分析它的源码不仅可以更好的使用它在出错时及时定位也可以在需要个性化功能时轻车熟路的修改它以满足我们自己的需求同时也可以学习人家解决问题的思路提升自己的技术水平
若依提供了很多实用且不花哨的注解本文记录了其中的一个注解RateLimiter--限流注解的实现步骤
版本说明
以下源码内容是基于RuoYi-Vue-3.8.2版本即前后端分离版本
主要思想
标注了RateLimiter注解的方法在执行前调用lua脚本把一段时间内的访问次数存入redis并返回判断返回值是否大于设定的阈值大于则抛出异常由全局异常处理器处理
具体步骤
1. 注解
我们先来看一看RateLimiter注解在src/main/java/com/ruoyi/common/annotation包下
Target(ElementType.METHOD)
Retention(RetentionPolicy.RUNTIME)
Documented
public interface RateLimiter
{// 限流keypublic String key() default Constants.RATE_LIMIT_KEY;// 限流时间,单位秒public int time() default 60;// 限流次数public int count() default 100;// 限流类型public LimitType limitType() default LimitType.DEFAULT;
}
一个作用在方法上的注解有四个属性
key存储在redis里用到的keytime限流时间相当于redis里的有效期count限流次数limitType: 限流类型点开枚举发现有默认和IP两种限流方式这两种方式的实现只是存储在redis里的key不同
2. 切面
我们来看一看RateLimiter这个注解的切面RateLimiterAspect.java在src/main/java/com/ruoyi/framework/aspectj包里
Aspect
Component
public class RateLimiterAspect
{private static final Logger log LoggerFactory.getLogger(RateLimiterAspect.class);private RedisTemplateObject, Object redisTemplate;private RedisScriptLong limitScript;Autowiredpublic void setRedisTemplate1(RedisTemplateObject, Object redisTemplate){this.redisTemplate redisTemplate;}Autowiredpublic void setLimitScript(RedisScriptLong limitScript){this.limitScript limitScript;}Before(annotation(rateLimiter))public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable{String key rateLimiter.key();int time rateLimiter.time();int count rateLimiter.count();String combineKey getCombineKey(rateLimiter, point);ListObject keys Collections.singletonList(combineKey);try{// 调用lua脚本传入三个参数Long number redisTemplate.execute(limitScript, keys, count, time);if (StringUtils.isNull(number) || number.intValue() count){throw new ServiceException(访问过于频繁请稍候再试);}log.info(限制请求{},当前请求{},缓存key{}, count, number.intValue(), key);}catch (ServiceException e){throw e;}catch (Exception e){throw new RuntimeException(服务器限流异常请稍候再试);}}public String getCombineKey(RateLimiter rateLimiter, JoinPoint point){// 获取注解中的key值StringBuffer stringBuffer new StringBuffer(rateLimiter.key());// 判断限流类型如果是IP限流就在key后添加上IP若依自己写了一个获取ip的方法类大家可以自行查看if (rateLimiter.limitType() LimitType.IP){stringBuffer.append(IpUtils.getIpAddr(ServletUtils.getRequest())).append(-);}// 获取方法MethodSignature signature (MethodSignature) point.getSignature();Method method signature.getMethod();// 获取类Class? targetClass method.getDeclaringClass();// key中添加方法名-类名stringBuffer.append(targetClass.getName()).append(-).append(method.getName());return stringBuffer.toString();}
}
简单说明一下这个切面类
使用了set的方式注入了RedisTemplate和RedisScriptRedisTemplate大家都很熟悉RedisScript是用于加载和执行lua脚本的定义了一个前置通知废话限流肯定是前置通过getCombineKey方法获取应该存入redis中的keygetCombineKey方法每一步我都做了注解将key、time、count作为参数传入lua脚本执行脚本判断返回值为空或者或者返回值大于设定的count抛出异常由全局异常处理器处理方法不再往下执行达到了限流的效果
3. lua脚本
最后我们来看一看若依是怎么写lua脚本的在脚本在redis的配置类RedisConfig.java里该类在src/main/java/com/ruoyi/framework/config包下
Configuration
EnableCaching
public class RedisConfig extends CachingConfigurerSupport
{……Beanpublic DefaultRedisScriptLong limitScript(){// 泛型是返回值的类型DefaultRedisScriptLong redisScript new DefaultRedisScript();// 设置脚本redisScript.setScriptText(limitScriptText());// 设置返回值类型redisScript.setResultType(Long.class);return redisScript;}/*** 限流脚本*/private String limitScriptText(){return local key KEYS[1]\n local count tonumber(ARGV[1])\n local time tonumber(ARGV[2])\n local current redis.call(get, key);\n if current and tonumber(current) count then\n return tonumber(current);\n end\n current redis.call(incr, key)\n if tonumber(current) 1 then\n redis.call(expire, key, time)\n end\n return tonumber(current);;}
}
我们主要看下lua脚本
接收3个变量key阈值count过期时间time调用get(key)方法获取key中的值current如果这个key存在并且current大于count返回current调用redis的自增函数赋值给current当current1时即第一次访问该接口调用redis的设置过期时间函数给当前key设置过期时间返回current
使用lua脚本可以在并发的情况下更好的满足原子性只是我不太明白若依为什么不把脚本文件单独拿出来写在resources文件夹下这样阅读和维护都会更加方便。总之这就是若依限流注解的全部内容
总结
标注了RateLimiter注解的方法在执行方法前调用lua脚本把自己的类名方法名当做key传入判断返回值是否大于设定的阈值大于则抛出异常不再向下执行异常由全局异常处理器处理。