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.rhkb.cn/news/494827.html

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

相关文章

010 Qt_输入类控件(LineEdit、TextEdit、ComboBox、SpinBox、DateTimeEdit、Dial、Slider)

文章目录 前言一、QLineEdit1.简介2.常见属性及说明3.重要信号及说明4.示例一&#xff1a;用户登录界面5.示例二&#xff1a;验证两次输入的密码是否一致显示密码 二、TextEdit1.简介2.常见属性及说明3.重要信号及说明4.示例一&#xff1a;获取多行输入框的内容5.示例二&#x…

RabbitMQ 的7种工作模式

RabbitMQ 共提供了7种⼯作模式,进⾏消息传递,. 官⽅⽂档:RabbitMQ Tutorials | RabbitMQ 1.Simple(简单模式) P:⽣产者,也就是要发送消息的程序 C:消费者,消息的接收者 Queue:消息队列,图中⻩⾊背景部分.类似⼀个邮箱,可以缓存消息;⽣产者向其中投递消息,消费者从其中取出消息…

WebAPI编程(第一天,第二天)

WebAPI编程&#xff08;第一天&#xff0c;第二天&#xff09; day01 - Web APIs 1.1. Web API介绍 1.1.1 API的概念1.1.2 Web API的概念1.1.3 API 和 Web API 总结 1.2. DOM 介绍 1.2.1 什么是DOM1.2.2. DOM树 1.3. 获取元素 1.3.1. 根据ID获取1.3.2. 根据标签名获取元素1.3.…

java如何使用poi-tl在word模板里渲染多张图片

1、poi-tl官网地址 http://deepoove.com/poi-tl/ 2、引入poi-tl的依赖 <dependency><groupId>com.deepoove</groupId><artifactId>poi-tl</artifactId><version>1.12.1</version></dependency>3、定义word模板 释义&#xf…

web三、 window对象,延时器,定时器,时间戳,location对象(地址),本地存储-localStorage,数组去重new Set

一、window对象 window对象 是一个全局对象&#xff0c;也可以说是JavaScript中的 顶级对象 像document、alert()、console.log()这些都是window的属性&#xff0c;基本BOM的属性和方法都是window的 所有通过 var定义 在全局作用域中的 变量 、 函数 都会变成window对象的属…

利用Spring Cloud Gateway Predicate优化微服务路由策略

利用Spring Cloud Gateway Predicate优化微服务路由策略 一、Predicate简介 Spring Cloud Gateway 是 Spring 生态系统中用于构建 API 网关的框架&#xff0c;它基于 Project Reactor 和 Netty 构建&#xff0c;旨在提供一种高效且灵活的方式来处理 HTTP 请求和响应。 Spring …

C#代码实现把中文录音文件(.mp3 .wav)转为文本文字内容

我们有一个中文录音文件.mp3格式或者是.wav格式&#xff0c;如果我们想要提取录音文件中的文字内容&#xff0c;我们可以采用以下方法&#xff0c;不需要使用Azure Speech API 密钥注册通过离线的方式实现。 1.首先我们先在NuGet中下载两个包 NAudio 2.2.1、Whisper.net 1.7.3…

CASA(Carnegie-Ames-Stanford Approach) 模型原理及实践

植被作为陆地生态系统的重要组成部分对于生态环境功能的维持具有关键作用。植被净初级生产力&#xff08;Net Primary Productivity, NPP&#xff09;是指单位面积上绿色植被在单位时间内由光合作用生产的有机质总量扣除自养呼吸的剩余部分。植被NPP是表征陆地生态系统功能及可…

申请腾讯混元的API Key并且使用LobeChat调用混元AI

申请腾讯混元的API Key并且使用LobeChat调用混元AI 之前星哥写了一篇文章《手把手教拥有你自己的大模型ChatGPT和Gemini等应用-开源lobe-chat》搭建的开源项目&#xff0c;今天这篇文章教大家如何添加腾讯云的混元模型&#xff0c;并且使用LobeChat调用腾讯混元AI。 申请腾讯混…

alertmanager告警持久化方案:alertsnitch

Prometheus告警记录持久化 Prometheus将基于告警规则生成的告警存储为时间序列&#xff0c;不会将Alertmanager的告警信息持久化存储&#xff0c; 那么针对历史告警的检索、统计等需求就无法实现。因此需要一种持久化机制用于存储历史告警信息&#xff0c; 本文主要探究基于al…

springboot481基于springboot社区老人健康信息管理系统(论文+源码)_kaic

摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统社区老人健康信息管理系统信息管理难度大&#xff0c;容错…

109.【C语言】数据结构之求二叉树的高度

目录 1.知识回顾&#xff1a;高度&#xff08;也称深度&#xff09; 2.分析 设计代码框架 返回左右子树高度较大的那个的写法一:if语句 返回左右子树高度较大的那个的写法二:三目操作符 3.代码 4.反思 问题 出问题的代码 改进后的代码 执行结果 1.知识回顾&#xf…

重温设计模式--享元模式

文章目录 享元模式&#xff08;Flyweight Pattern&#xff09;概述享元模式的结构C 代码示例1应用场景C示例代码2 享元模式&#xff08;Flyweight Pattern&#xff09;概述 定义&#xff1a; 运用共享技术有效地支持大量细粒度的对象。 享元模式是一种结构型设计模式&#xff0…

Pytorch | 从零构建EfficientNet对CIFAR10进行分类

Pytorch | 从零构建EfficientNet对CIFAR10进行分类 CIFAR10数据集EfficientNet设计理念网络结构性能特点应用领域发展和改进 EfficientNet结构代码详解结构代码代码详解MBConv 类初始化方法前向传播 forward 方法 EfficientNet 类初始化方法前向传播 forward 方法 训练过程和测…

【教程】第十一章 子任务 工时——化繁为简

小伙伴们&#xff0c;终于迎来了新章节&#xff01;随着业务的扩展&#xff0c;任务越来越多&#xff0c;越来越复杂&#xff0c;我们逐渐意识到&#xff0c;简单的任务管理已经不够用了。现在&#xff0c;我们需要对任务进行更细致的管理&#xff0c;分解成多个层级&#xff0…

git clone必须使用sudo否则失败 git推送错误想再次编辑和推送

git clone必须使用sudo否则失败 我的问题比较特别用env | grep -i proxy发现没问题所幸直接删掉~/.ssh下的秘钥&#xff0c;重新弄 搭建SSH秘钥方法: &#xff08;一&#xff09;配置git 操作&#xff1a;linux镜像--桌面--右键--打开终端。 > git config --global user.n…

Docker搭建kafka环境

系统&#xff1a;MacOS Sonoma 14.1 Docker版本&#xff1a;Docker version 27.3.1, build ce12230 Docker desktop版本&#xff1a;Docker Desktop 4.36.0 (175267) 1.拉取镜像 先打开Docker Desktop&#xff0c;然后在终端执行命令 docker pull lensesio/fast-data-dev …

Java复习|图形用户界面AWT、Swing----银行客户管理系统【校课版】【1】

校课总结&#xff0c;部分&#xff0c;未完待续...... 背景了解 Java的AWT和Swing的现状 AWT&#xff08;Abstract Window Toolkit&#xff09; AWT是Java中最早期的图形用户界面&#xff08;GUI&#xff09;工具包&#xff0c;它直接与操作系统提供的图形函数进行交互&a…

cudnn版本gpu架构

nvcc --help 可以看 --gpu-architecture 写到的支持的架构 NVIDIA 的 GPU 架构是按代次发布的&#xff0c;以下是这些架构的对应说明&#xff1a; NVIDIA Hopper: 这是 NVIDIA 于 2022 年推出的架构之一&#xff0c;面向高性能计算&#xff08;HPC&#xff09;和人工智能&…

视频汇聚融合云平台Liveweb一站式解决视频资源管理痛点

随着5G技术的广泛应用&#xff0c;各领域都在通信技术加持下通过海量终端设备收集了大量视频、图像等物联网数据&#xff0c;并通过人工智能、大数据、视频监控等技术方式来让我们的世界更安全、更高效。然而&#xff0c;随着数字化建设和生产经营管理活动的长期开展&#xff0…