Redis+注解实现限流机制(IP、自定义等)

简介

在项目的使用过程中,限流的场景是很多的,尤其是要提供接口给外部使用的时候,但是自己去封装的话,相对比较耗时。

本方式可以使用默认(方法),ip、自定义参数进行限流,根据时间和次数进行。

整合步骤

依赖

  <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.27</version></dependency><dependency><groupId>com.googlecode.aviator</groupId><artifactId>aviator</artifactId><version>5.4.1</version><scope>compile</scope></dependency>

限流注解

package com.walker.ratelimiter.annotation;import com.walker.ratelimiter.enums.LimitType;import java.lang.annotation.*;@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimiter {/*** 限流key*/String key() default "rate_limit";/*** 限流时间,单位秒*/int time() default 60;/*** 限流次数*/int count() default 50;/*** 限流类型*/LimitType limitType() default LimitType.DEFAULT;/*** 自定义编码* 支持SPEL表达式* 如果使用多参数,则使用:分割**/String customerCode() default "";/*** 自定义编码分割符*/String customerCodeSplit() default ":";
}

限流配置:获取限流lua脚本

package com.walker.ratelimiter.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;@Configuration
public class RateLimitConfig {@Beanpublic DefaultRedisScript<Long> limitScript() {DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/limit.lua")));redisScript.setResultType(Long.class);return redisScript;}}

基础变量

package com.walker.ratelimiter.constants;public interface BaseConstants {String COLON = ":";
}

枚举类型

package com.walker.ratelimiter.enums;public enum LimitType {/*** 默认策略*/DEFAULT,/*** 根据IP进行限流*/IP,/*** 自定义*/CUSTOME,}

lua脚本

local key = KEYS[1]
local count = tonumber(ARGV[1])
local time = tonumber(ARGV[2])
local current = redis.call('get', key)
if current and tonumber(current) > count thenreturn tonumber(current)
end
current = redis.call('incr', key)
if tonumber(current) == 1 thenredis.call('expire', key, time)
end
return tonumber(current)

切面类

package com.walker.ratelimiter.aspect;import cn.hutool.core.util.StrUtil;
import com.walker.ratelimiter.annotation.RateLimiter;
import com.walker.ratelimiter.constants.BaseConstants;
import com.walker.ratelimiter.enums.LimitType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.List;@Slf4j
@Aspect
@Component
public class RateLimiterAspect {private final RedisTemplate redisTemplate;private final RedisScript<Long> limitScript;private SpelExpressionParser spelExpressionParser = new SpelExpressionParser();public RateLimiterAspect(RedisTemplate redisTemplate, RedisScript<Long> limitScript) {this.redisTemplate = redisTemplate;this.limitScript = limitScript;}@Around("@annotation(com.walker.ratelimiter.annotation.RateLimiter)")public Object doBefore(ProceedingJoinPoint joinPoint) throws Throwable {MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();RateLimiter rateLimiter = methodSignature.getMethod().getAnnotation(RateLimiter.class);//判断该方法是否存在限流的注解if (null != rateLimiter) {//获得注解中的配置信息int count = rateLimiter.count();int time = rateLimiter.time();//调用getCombineKey()获得存入redis中的key   key -> 注解中配置的key前缀-ip地址-方法路径-方法名String combineKey = getCombineKey(rateLimiter, methodSignature, joinPoint);log.info("combineKey->,{}", combineKey);//将combineKey放入集合List<Object> keys = Collections.singletonList(combineKey);log.info("keys->", keys);try {//执行lua脚本获得返回值Long number = (Long) redisTemplate.execute(limitScript, keys, count, time);//如果返回null或者返回次数大于配置次数,则限制访问if (number == null || number.intValue() > count) {throw new RuntimeException("访问过于频繁,请稍候再试");}log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), combineKey);} catch (RuntimeException e) {throw e;} catch (Exception e) {throw new RuntimeException("服务器限流异常,请稍候再试");}}return joinPoint.proceed();}/*** Gets combine key.** @param rateLimiter the rate limiter* @param signature   the signature* @param joinPoint* @return the combine key*/public String getCombineKey(RateLimiter rateLimiter, MethodSignature signature, ProceedingJoinPoint joinPoint) throws UnknownHostException {StringBuilder stringBuffer = new StringBuilder(rateLimiter.key());
//        ip限流if (rateLimiter.limitType() == LimitType.IP) {InetAddress ip = InetAddress.getLocalHost();log.info("获取ip地址为:{}", ip);String hostAddress = ip.getHostAddress();stringBuffer.append(hostAddress).append(BaseConstants.COLON);
//        自定义编码限流} else if (rateLimiter.limitType() == LimitType.CUSTOME) {if (StrUtil.isEmpty(rateLimiter.customerCode())) {throw new RuntimeException("自定义编码不能为空");}String customerCode = rateLimiter.customerCode();String split = rateLimiter.customerCodeSplit();String[] customerCodes = customerCode.split(split);for (String code : customerCodes) {ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();EvaluationContext evaluationContext = new MethodBasedEvaluationContext(TypedValue.NULL, signature.getMethod(), joinPoint.getArgs(), parameterNameDiscoverer);Expression expression = spelExpressionParser.parseExpression(code);String resolvedCustomerCode =  String.valueOf(expression.getValue(evaluationContext));if(StrUtil.isEmpty(resolvedCustomerCode)){throw new RuntimeException("自定义编码不能为空");}stringBuffer.append(BaseConstants.COLON).append(resolvedCustomerCode);}}Method method = signature.getMethod();Class<?> targetClass = method.getDeclaringClass();stringBuffer.append(BaseConstants.COLON).append(targetClass.getName()).append(BaseConstants.COLON).append(method.getName());return stringBuffer.toString();}}

使用

  • 根据ip进行限流

limitType = LimitType.IP

  • 默认

limitType = LimitType.DEFAULT

  • 自定义参数限流

使用Spel表达式,从参数中获取自定义的code,然后60s限流5次

@RateLimiter(limitType = LimitType.CUSTOME,customerCode = "#form.appCode:#toUserInfo.userUid",count = 5,time = 60)
public Result<Boolean> message(ImSendMsgForm form, MissuUsers toUserInfo) {}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/64964.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

仿闲鱼的二手交易小程序软件开发闲置物品回收平台系统源码

市场前景 闲置物品交易软件的市场前景广阔&#xff0c;主要基于以下几个方面的因素&#xff1a; 环保意识提升&#xff1a;随着人们环保意识的增强&#xff0c;越来越多的人开始关注资源的循环利用&#xff0c;闲置物品交易因此受到了广泛的关注。消费升级与时尚节奏加快&…

FastJson读取resources下的json文件并且转成对象

读取resources下的json文件并且转成对象 json文件路径是: ​​ ‍ 读取代码 ‍ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.TypeReference; import lombok.extern.slf4j.Slf4j; import org.apache.commons.…

深圳龙岗戴尔dell r730xd服务器故障维修

深圳龙岗一台DELL POWEREDGE R730XD服务器系统故障问题处理&#xff1a; 1&#xff1a;客户工厂年底产线整改&#xff0c;时不时的会意外断电&#xff0c;导致服务器也频繁停机&#xff0c; 2&#xff1a;多次异常停机后导致服务器开机后windows server系统无法正常启动了&…

绕组识别标签规范

有标签名称的要标记&#xff0c;没有的不用标记 需要标注的工具、器材 图像中文名称标签名称od脱模剂watering can2铁铲shovel1记号笔&#xff0c;白色着重标bluepen/whitepen6纸质标签label3钢尺scale5玻璃纤维带&#xff08;卷&#xff09;红色网格布red grid4白色网格布wh…

中国信通院致信感谢易保全:肯定贡献能力,期许未来合作

近日&#xff0c;中国信息通信研究院&#xff08;以下简称“中国信通院”&#xff09;向易保全发感谢信表达谢意&#xff0c;对其在中国信通院牵头的“铸基计划”——企业数字化转型高质量发展推进行动实施中展现出的重要贡献给予了高度评价和肯定&#xff0c;并展望了双方至20…

WebRTC服务质量(08)- 重传机制(05) RTX机制

一、前言&#xff1a; RTX协议&#xff08;Retransmission&#xff0c;即重传协议&#xff09;是 WebRTC 中用于处理丢包恢复的一部分。由于网络通信中的丢包不可避免&#xff0c;WebRTC RTP协议栈支持多种丢包恢复机制&#xff0c;其中之一便是通过RTX协议实现的重传机制。 …

国自然联合项目|影像组学智能分析理论与关键技术|基金申请·24-12-25

小罗碎碎念 该项目为国自然联合基金项目&#xff0c;执行年限为2019年1月至2022年12月&#xff0c;直接费用为204万元。 项目研究内容包括影像组学分析、智能计算、医疗风险评估等&#xff0c;旨在通过模拟医生诊断过程&#xff0c;推动人工智能在医疗领域的创新。 项目取得了…

轮播图带详情插件、uniApp插件

超级好用的轮播图 介绍访问地址参数介绍使用方法&#xff08;简单使用&#xff0c;参数结构点击链接查看详情&#xff09;图片展示 介绍 带有底部物品介绍以及价格的轮播图组件&#xff0c;持续维护&#xff0c;uniApp插件&#xff0c;直接下载填充数据就可以在项目里面使用 …

Java 本地缓存实现:Guava Cache、Caffeine、Ehcache 和 Spring Cache

文章目录 一、引言二、Guava Cache理论介绍实战演示 三、Caffeine理论介绍实战演示 四、Ehcache理论介绍实战演示 五、Spring Cache理论介绍实战演示 六、总结 一、引言 在现代应用程序开发中&#xff0c;缓存是提高性能和响应速度的关键技术之一。Java 提供了多种本地缓存解决…

计算机网络B重修班-期末复习

[TOC] (计算机网络B重修班-期末复习&#xff09; 一、单选 &#xff08;20题&#xff0c;1分/题&#xff0c;共20分&#xff09; 二、判断 &#xff08;10题&#xff0c;1分/题&#xff0c;共10分&#xff09; 三、填空 &#xff08;10题&#xff0c;1分/题&#xff0c;共10…

QT的前景与互联网岗位发展

qt是用来干什么的 --》桌面应用开发&#xff08;做电脑的应用程序&#xff0c;面对客户端&#xff09;。 主要用于开发跨平台的应用程序和用户界面&#xff08;UI&#xff09;。它是一个全面的C库集合&#xff0c;提供了构建软件应用所需的各种工具和功能。 客户端开发的重…

重温设计模式--单例模式

文章目录 单例模式&#xff08;Singleton Pattern&#xff09;概述单例模式的实现方式及代码示例1. 饿汉式单例&#xff08;在程序启动时就创建实例&#xff09;2. 懒汉式单例&#xff08;在第一次使用时才创建实例&#xff09; 单例模式的注意事项应用场景 C代码懒汉模式-经典…

Java字符串的|分隔符转List实现方案

字符串处理 问题背景代码实现代码优化原因分析实现方案 注意事项异常处理Maven未识别异常 问题背景 在项目组对账流程中&#xff0c;接收对方系统的对账文件&#xff0c;数据以|为分隔符&#xff0c;读取文件内容&#xff0c;分条入库。 代码实现 Java中将字符串转给list&am…

项目底链华为链切换长安链经验总结

项目底链华为链切换长安链经验总结 前言业务需求分析智能合约重写k-v存储结构设计设计上链存储的结构体使用迭代器查询历史记录长安链合约编辑器历史记录返回错误材料上链非必传字段 Int 类型自动赋值长安链cmc工具部署合约ca证书需齐全分页查询截取处理&#xff0c;返回 nil处…

【机器学习】从流动到恒常,无穷中归一:积分的数学诗意

文章目录 微积分基础&#xff1a;理解变化与累积的数学前言一、积分概述与基础概念1.1 积分的定义与重要性1.1.1 积分的基本组成1.1.2 积分在机器学习中的应用 1.2 积分的历史与发展 二、积分的基本概念与计算2.1 不定积分2.1.1 不定积分的定义2.1.2 不定积分的计算方法2.1.3 实…

在瑞芯微RK3588平台上使用RKNN部署YOLOv8Pose模型的C++实战指南

在人工智能和计算机视觉领域,人体姿态估计是一项极具挑战性的任务,它对于理解人类行为、增强人机交互等方面具有重要意义。YOLOv8Pose作为YOLO系列中的新成员,以其高效和准确性在人体姿态估计任务中脱颖而出。本文将详细介绍如何在瑞芯微RK3588平台上,使用RKNN(Rockchip N…

电磁兼容(EMC):一文解读磁芯复合材料——塑磁

目录 01 塑磁的定义 02 塑磁的常见规格型号 03 塑磁材料的优点 04 塑磁的应用 塑磁,也称为注塑磁,是一种将磁性粉末注入到塑料基体中制成的复合磁体材料。以下是塑磁的定义、应用和材料特性的总结: 01 塑磁的定义 塑磁是以塑料为基体,通过特殊工艺在其中加入磁性粒子(…

五种msvcr100.dll丢失的解决方法,有效修复msvcr100.dll丢失错误!跟msvcr100.dll错误问题说拜拜!

在日常电脑使用过程中&#xff0c;尤其是运行某些应用程序或游戏时&#xff0c;可能会遇到“msvcr100.dll丢失”的错误提示。这个动态链接库&#xff08;DLL&#xff09;文件是Microsoft Visual C Redistributable for Visual Studio 2010的一部分&#xff0c;对于许多程序的正…

redis数据类型:list

数据结构 源码版本&#xff1a;7.2.2路径&#xff1a;src/adlist.h 关于list的 头文件中涉及到的这三个结构体如下 /* Node, List, and Iterator are the only data structures used currently. */ # 节点 typedef struct listNode {struct listNode *prev; # 前元素的指针s…

计算机网络(网络层)

1、ARP协议(已知ip地址得到mac地址) 在传输一个 IP 数据报的时候&#xff0c;确定了源 IP 地址和目标 IP 地址后&#xff0c;就会 通过主机「路由表」确定 IP 数据包下一跳。然而&#xff0c;网络层的下一层是数据链路层&#xff0c;所以我们还要知道「下一跳」的 MAC 地址。由…