当前位置: 首页 > news >正文

若依框架----源码分析(@RateLimiter)

若依作为最近非常火的脚手架,分析它的源码,不仅可以更好的使用它,在出错时及时定位,也可以在需要个性化功能时轻车熟路的修改它以满足我们自己的需求,同时也可以学习人家解决问题的思路,提升自己的技术水平

若依提供了很多实用且不花哨的注解,本文记录了其中的一个注解@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里用到的key
  • time:限流时间,相当于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 RedisTemplate<Object, Object> redisTemplate;private RedisScript<Long> limitScript;@Autowiredpublic void setRedisTemplate1(RedisTemplate<Object, Object> redisTemplate){this.redisTemplate = redisTemplate;}@Autowiredpublic void setLimitScript(RedisScript<Long> 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);List<Object> 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();}
}

简单说明一下这个切面类:

  1. 使用了set的方式注入了RedisTemplateRedisScriptRedisTemplate大家都很熟悉,RedisScript是用于加载和执行lua脚本的
  2. 定义了一个前置通知(废话,限流肯定是前置),通过getCombineKey方法获取应该存入redis中的key,getCombineKey方法每一步我都做了注解
  3. 将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 DefaultRedisScript<Long> limitScript(){// 泛型是返回值的类型DefaultRedisScript<Long> 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脚本:

  1. 接收3个变量:key,阈值count,过期时间time
  2. 调用get(key)方法获取key中的值current,如果这个key存在并且current大于count,返回current
  3. 调用redis的自增函数赋值给current,当current=1时(即第一次访问该接口),调用redis的设置过期时间函数给当前key设置过期时间
  4. 返回current

使用lua脚本可以在并发的情况下更好的满足原子性,只是我不太明白若依为什么不把脚本文件单独拿出来写在resources文件夹下,这样阅读和维护都会更加方便。总之,这就是若依限流注解的全部内容

总结

标注了@RateLimiter注解的方法,在执行方法前调用lua脚本,把自己的类名+方法名当做key传入,判断返回值是否大于设定的阈值,大于则抛出异常不再向下执行,异常由全局异常处理器处理。

相关文章:

若依框架----源码分析(@RateLimiter)

若依作为最近非常火的脚手架&#xff0c;分析它的源码&#xff0c;不仅可以更好的使用它&#xff0c;在出错时及时定位&#xff0c;也可以在需要个性化功能时轻车熟路的修改它以满足我们自己的需求&#xff0c;同时也可以学习人家解决问题的思路&#xff0c;提升自己的技术水平…...

页面的重排和重绘?

思路&#xff1a; 网页渲染HTML文件到浏览器的过程->定义->如何优化网页渲染HTML文件到浏览器的过程HTML 文件通过HTML解析器解析生成DOM树&#xff1b;CSS文件通过CSS解析器生成CSSOM树&#xff1b;DOM树和CSSOM树生成渲染树&#xff08;render tree&#xff09;&#x…...

人脸检测-python和c++实现

人脸检测是计算机视觉领域中的一个重要应用,其目的是从图像或视频中自动检测出其中的人脸,并对其进行识别、跟踪等操作。人脸检测技术已经广泛应用于安防、人机交互、娱乐等领域,具有广泛的应用前景。 人脸检测的基本思路可以分为以下几个步骤: 图像预处理:首先需要对输入…...

PowerJob源码环境搭建

一、IEDA导入PowerJob源码 gitgithub.com:PowerJob/PowerJob.gitPowerJob 由调度服务器&#xff08;powerjob-server&#xff09;和执行器&#xff08;powerjob-worker&#xff09;两部分组成 powerjob-server 负责提供 Web 服务和完成任务的调度powerjob-worker 则负责执行用…...

天梯赛刷题小记 —— L2

最近在重刷 天梯赛&#xff0c;浅浅记录一下&#xff0c;进入L2阶段了 L2-001 紧急救援 解题思路&#xff1a;典型的dijkstra模板题&#xff0c;带路径记录与权重&#xff0c;方案数记录&#xff0c;解析出过 Dijkstra(兼路径) #include <bits/stdc.h> #define inf…...

Prometheus监控实战系列十九:监控Kubernetes集群(上)

Kuberentes是一款开源的容器编排产品&#xff0c;由Google开发后发布到社区&#xff0c;并在2015年将该项目捐献给了云原生基金会&#xff08;Cloud Native Computing Foundation&#xff09;。从2014年第一个版本发布以来&#xff0c;Kubernetes便迅速获得开源社区的追捧&…...

番茄学习法——亲测超级好用

今天给大家分享下我最近使用的学习方法&#xff0c;真的非常好用&#xff01;大家用起来&#xff01; 在日常的学习和工作中&#xff0c;我们经常会遇到一些难以克服的问题&#xff1a;分心、效率低下、焦虑等。为了帮助人们更好地学习和工作&#xff0c;一些学习方法和工具应运…...

vue 项目中使用高德地图

一、账号准备 首先&#xff0c;需要注册并登录高德地图开放平台&#xff0c;申请密钥。操作指引&#xff1a;高德地图开放平台 二、安装高德地图加载器 npm 安装&#xff1a; npm i amap/amap-jsapi-loader --save或者 yarn 安装&#xff1a; yarn add amap/amap-jsapi-loa…...

【每日一题】病人排队

题目描述小理是个热爱生活的孩子。病人登记看病&#xff0c;小理想编写一个程序&#xff0c;将登记的病人按照以下原则排出看病的先后顺序&#xff1a;1. 老年人&#xff08;年龄 ≥≥ 60岁&#xff09;比非老年人优先看病。2. 老年人按年龄从大到小的顺序看病&#xff0c;年龄…...

【数据结构】链表OJ题

目录面试题 02.04 分割链表剑指 Offer II 027 回文链表160 相交链表141 环形链表142 环形链表 II138 复制带随机指针的链表面试题 02.04 分割链表 定义lesshead和greaterhead链接小于和大于等于k的值分别设置哨兵位和尾节点指针最后将两表去除哨兵位再链接 struct ListNode* p…...

冒泡 VS 插入 VS 选择——谁更胜一筹?(附排序源码)

文章目录什么样的“排序算法”更加优质&#xff1f;排序算法的执行效率排序算法的内存消耗排序算法的稳定性冒泡排序&#xff08;Bubble Sort&#xff09;插入排序&#xff08;Insertion Sort&#xff09;选择排序&#xff08;Selection Sort&#xff09;最终的胜利者&#x1f…...

[python tools] 今天看到另一个配置工具 YACS,所以做下笔记

YACS 实际上就只是把别人的readme翻译了一下 github: https://github.com/rbgirshick/yacs 样例代码: https://github.com/Wuziyi616/multi_part_assembly/blob/master/docs/config.md 一、使用方法 1. 首先搞一个config的python文件&#xff0c;用来存一下基本的配置信息 比…...

Prometheus cadvisor容器监控和node-exporter节点监控

往期文章 Prometheus监控系统 https://blog.csdn.net/qq_39578545/article/details/108754585 Docker之compose介绍 使用一个Dockerfile模板文件可以定义一个单独的应用容器&#xff0c;如果需要定义多个容器就需要服务编排。下面介绍Docker官方产品&#xff0c;Docker Comp…...

机器学习|正则化|评估方法|分类模型性能评价指标|吴恩达学习笔记

前文回顾&#xff1a;逻辑回归 目录 &#x1f4da;正则化 &#x1f407;过拟合的问题 &#x1f407;代价函数 &#x1f407;正则化线性回归 &#x1f407;正则化的逻辑回归模型 &#x1f4da;模型评估方法 &#x1f407;留出法&#xff08;hold-out&#xff09; &#…...

python迭代器详解

不懂的问题&#xff1a;什么是协变、逆变&#xff1f;渐进式&#xff1f; _T_co TypeVar("_T_co", covariantTrue) # Any type covariant containers.作者&#xff1a;20岁爱吃必胜客&#xff08;坤制作人&#xff09;&#xff0c;近十年开发经验, 跨域学习者&…...

关于Docker逃逸

关于Docker逃逸 文章目录关于Docker逃逸前言一、判断是否为docker容器&#xff1f;二、privileged特权模式启动容器逃逸三、 Docker Remote API未授权访问逃逸四、危险挂载导致Docker逃逸五、危险挂载Docker Socket逃逸六、 挂载宿主机procfs逃逸七、脏牛漏洞来进行docker逃逸八…...

@Autowired和@Resource区别

Autowired和Resource到底有什么区别 Autowired 和 Resource 都是用来实现依赖注入的注解&#xff08;在 Spring/Spring Boot 项目中&#xff09;&#xff0c;但二者却有着 5 点不同&#xff1a; 来源不同&#xff1a;Autowired 来自 Spring 框架&#xff0c;而 Resource 来自…...

动态内存管理详细讲解

目录 1.为什么存在动态内存分配 2. 动态内存函数的介绍 2.1 malloc和free 2.2 calloc 2.3 realloc 今天要和大家分享的内容是的动态内存管理&#xff0c;我们先从他的定义入手学习。 1.为什么存在动态内存分配 我们到现在已经掌握了内存开辟的方式就是要么创建一个变量…...

Python和Excel的完美结合:常用操作汇总(案例详析)

在以前&#xff0c;商业分析对应的英文单词是Business Analysis&#xff0c;大家用的分析工具是Excel&#xff0c;后来数据量大了&#xff0c;Excel应付不过来了&#xff08;Excel最大支持行数为1048576行&#xff09;&#xff0c;人们开始转向python和R这样的分析工具了&#…...

卡特兰数、斯特林数基础

卡特兰数 从格点(0,0)(0,0)(0,0)走到格点(n,n)(n,n)(n,n)&#xff0c;只能向右或向上走&#xff0c;不能穿过对角线&#xff0c;的路径的条数&#xff0c;称为卡特兰数HnH_nHn​。 则有H01H_01H0​1。 通项公式&#xff1a; Hn(2nn)−(2nn−1)H_n\begin{pmatrix} 2n\\ n \en…...

KubeSphere 容器平台高可用:环境搭建与可视化操作指南

Linux_k8s篇 欢迎来到Linux的世界&#xff0c;看笔记好好学多敲多打&#xff0c;每个人都是大神&#xff01; 题目&#xff1a;KubeSphere 容器平台高可用&#xff1a;环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...

Ubuntu系统下交叉编译openssl

一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机&#xff1a;Ubuntu 20.04.6 LTSHost&#xff1a;ARM32位交叉编译器&#xff1a;arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...

【杂谈】-递归进化:人工智能的自我改进与监管挑战

递归进化&#xff1a;人工智能的自我改进与监管挑战 文章目录 递归进化&#xff1a;人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管&#xff1f;3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

oracle与MySQL数据库之间数据同步的技术要点

Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异&#xff0c;它们的数据同步要求既要保持数据的准确性和一致性&#xff0c;又要处理好性能问题。以下是一些主要的技术要点&#xff1a; 数据结构差异 数据类型差异&#xff…...

uniapp微信小程序视频实时流+pc端预览方案

方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度​WebSocket图片帧​定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐​RTMP推流​TRTC/即构SDK推流❌ 付费方案 &#xff08;部分有免费额度&#x…...

Python Ovito统计金刚石结构数量

大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...

[免费]微信小程序问卷调查系统(SpringBoot后端+Vue管理端)【论文+源码+SQL脚本】

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的微信小程序问卷调查系统(SpringBoot后端Vue管理端)【论文源码SQL脚本】&#xff0c;分享下哈。 项目视频演示 【免费】微信小程序问卷调查系统(SpringBoot后端Vue管理端) Java毕业设计_哔哩哔哩_bilibili 项…...

【JavaSE】多线程基础学习笔记

多线程基础 -线程相关概念 程序&#xff08;Program&#xff09; 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序&#xff0c;比如我们使用QQ&#xff0c;就启动了一个进程&#xff0c;操作系统就会为该进程分配内存…...

Linux nano命令的基本使用

参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时&#xff0c;显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...