接口防刷!利用redisson快速实现自定义限流注解
问题:
在日常开发中,一些重要的对外接口,需要加上访问频率限制,以免造成资��损失。
如登录接口,当用户使用手机号+验证码登录时,一般我们会生成6位数的随机验证码,并将验证码有效期设置为1-3分钟,如果对登录接口不加以限制,理论上,通过技术手段,快速重试100000次,即可将验证码穷举出来。
解决思路:对登录接口加上限流操作,如限制一分钟内最多登录5次,登录次数过多,就返回失败提示,或者将账号锁定一段时间。
实现手段:利用redis的有序集合即Sorted Set数据结构,构造一个令牌桶来实施限流。而redisson已经帮我们封装成了RRateLimiter,通过redisson,即可快速实现我们的目标。
定义一个限流注解
import org.redisson.api.RateIntervalUnit; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GlobalRateLimiter { String key(); long rate(); long rateInterval() default 1L; RateIntervalUnit rateIntervalUnit() default RateIntervalUnit.SECONDS; }
利用aop进行切面
import com.zj.demoshow.annotion.GlobalRateLimiter;
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.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.Redisson;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component; import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit; @Aspect
@Component
@Slf4j
public class GlobalRateLimiterAspect { @Resource
private Redisson redisson;
@Value("${spring.application.name}")
private String applicationName;
private final DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer(); @Pointcut(value = "@annotation(com.zj.demoshow.annotion.GlobalRateLimiter)")
public void cut() {
} @Around(value = "cut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
String className = method.getDeclaringClass().getName();
String methodName = method.getName();
GlobalRateLimiter globalRateLimiter = method.getDeclaredAnnotation(GlobalRateLimiter.class);
Object[] params = joinPoint.getArgs();
long rate = globalRateLimiter.rate();
String key = globalRateLimiter.key();
long rateInterval = globalRateLimiter.rateInterval();
RateIntervalUnit rateIntervalUnit = globalRateLimiter.rateIntervalUnit();
if (key.contains("#")) {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext ctx = new StandardEvaluationContext();
String[] parameterNames = discoverer.getParameterNames(method);
if (parameterNames != null) {
for (int i = 0; i < parameterNames.length; i++) {
ctx.setVariable(parameterNames[i], params[i]);
}
}
Expression expression = parser.parseExpression(key);
Object value = expression.getValue(ctx);
if (value == null) {
throw new RuntimeException("key无效");
}
key = value.toString();
}
key = applicationName + "_" + className + "_" + methodName + "_" + key;
log.info("设置限流锁key={}", key);
RRateLimiter rateLimiter = this.redisson.getRateLimiter(key);
if (!rateLimiter.isExists()) {
log.info("设置流量,rate={},rateInterval={},rateIntervalUnit={}", rate, rateInterval, rateIntervalUnit);
rateLimiter.trySetRate(RateType.OVERALL, rate, rateInterval, rateIntervalUnit);
//设置一个过期时间,避免key一直存在浪费内存,这里设置为延长5分钟
long millis = rateIntervalUnit.toMillis(rateInterval);
this.redisson.getBucket(key).expire(Long.sum(5 * 1000 * 60, millis), TimeUnit.MILLISECONDS);
}
boolean acquire = rateLimiter.tryAcquire(1);
if (!acquire) {
//这里直接抛出了异常 也可以抛出自定义异常,通过全局异常处理器拦截进行一些其他逻辑的处理
throw new RuntimeException("请求频率过高,此操作已被限制");
}
return joinPoint.proceed();
}
}
ok,通过以上两步,即可完成我们的限流注解了,下面通过一个接口验证下效果。
新建一个controller,写一个模拟登录的方法。
@RestController
@RequestMapping(value = "/user")
public class UserController {
@PostMapping(value = "/testForLogin")
//以account为锁的key,限制每分钟最多登录5次
@GlobalRateLimiter(key = "#params.account", rate = 5, rateInterval = 60)
R<Object> testForLogin(@RequestBody @Validated LoginParams params) {
//登录逻辑
return R.success("登录成功");
}
}
启动服务,通过postman访问此接口进行验证。

接口防刷!利用redisson快速实现自定义限流注解的更多相关文章
- 使用Redis+自定义注解实现接口防刷
最近开发了一个功能,需要发送短信验证码鉴权,考虑到短信服务需要收费,因此对此接口做了防刷处理,实现方式主要是Redis+自定义注解(需要导入Redis的相关依赖,完成Redis的相关配置,gs代码,这 ...
- Spring Boot项目的接口防刷
说明:使用了注解的方式进行对接口防刷的功能,非常高大上,本文章仅供参考 一,技术要点:springboot的基本知识,redis基本操作, 首先是写一个注解类: import java.lang.an ...
- Spring Boot 项目的 API 接口防刷
首先是写一个注解类 拦截器中实现 注册到springboot中 在Controller中加入注解 说明:使用了注解的方式进行对接口防刷的功能,非常高大上,本文章仅供参考 一,技术要点:springbo ...
- [转]Springboot项目的接口防刷的实例
来源:微信公众号 马士兵 原地址:https://mp.weixin.qq.com/s/tHQcWwIt4c41IUnvCQ2QWA 说明:使用了注解的方式进行对接口防刷的功能,非常高大上,本文章仅供 ...
- SpringCloud(六)之 网关概念、Zuul项目搭建-(利用Zuul 实现鉴权和限流实战)
一.网关概念 1.什么是路由网关 网关是系统的唯一对外的入口,介于客户端和服务器端之间的中间层,处理非业务功能 提供路由请求.鉴权.监控.缓存.限流等功能.它将"1对N"问题转换成 ...
- spring boot gateway自定义限流
参考:https://blog.csdn.net/ErickPang/article/details/84680132 采用自带默认网关请参照微服务架构spring cloud - gateway网关 ...
- 页面接口防刷 解决思路一nginx
线上环境 很多接口 如果不做缓存 可能导致有人拿到url 每秒几万次的访问后台程序,导致系统down机. 此处, nginx可以加一层缓存. expires起到控制页面缓存的作用,合理的配置expi ...
- SpringBoot接口防刷
一.自定义注解 import java.lang.annotation.Retention; import java.lang.annotation.Target; import static jav ...
- Springboot项目的接口防刷(实例)
技术要点:springboot的基本知识,redis基本操作, 首先是写一个注解类: import java.lang.annotation.Retention; import java.lang.a ...
- 不死的小强 .net core 微服务 快速开发框架 Viper 限流
1.Viper是什么? Viper 是.NET平台下的Anno微服务框架的一个示例项目.入门简单.安全.稳定.高可用.全平台可监控.底层通讯可以随意切换thrift grpc. 自带服务发现.调用链追 ...
随机推荐
- 🐬记一次MySQL执行修改语句超时问题
异常问题 原因分析 这个问题发生在开发环境,怀疑是提交事务时终止项目运行,没有提交该事务,造成死锁 调试该事务时时间太长,为什么说有这个原因呢,因为通过查找日志显示 The client was di ...
- CCL 2024 Task7 双任务冠军
近期参加NLP领域CCL2024评测,现将赛题背景和实现方法分享,推理文本纠错领域的发展. 1.背景信息 随着教育的发展和网络的普及,作文评价的规模越来越大,人工评改作文的成本和效率成为一大难题.为了 ...
- 用【Makefile】或【Cmake】编译【C/Fortran】程序文件
一.vimrc文件设置 set tabstop=4 "tab空格4 set expandtab "将tab扩展成空格 set softtabstop=4 "表示在编辑模式 ...
- redis 使用lua脚本 一次性获取多个hash key 字段值
客户端命令行代码: eval "local rst={};local field='schoolid'; for i,v in pairs(KEYS) do rst[i]=redis.cal ...
- 修改java版本环境变量不生效
修改java版本环境变量不生效 起因 先上图说明问题(电脑含有多个java版本,因为需要维护很老的项目,需要切换至以前的java版本,当然也可以不用修改环境变量,直接指定) 环境变量配置 正常 jav ...
- Mp4V2与ffmpeg静态库符号冲突问题解决
一.为什么静态符号会冲突 无论macho二进制类型,还是Windows上的PE格式,还是Linux上的ELF格式,里面都是按照特定格式存放的一个程序的代码和数据 比如Linux下的可执行文件格式,大致 ...
- JS中BOM事件,JS样式特效,表格对象和表单操作
DOM事件 1.DOM中的事件可以分为两类 1.浏览器行为 如:文档加载完成,图片加载完成 2.用户行为 如:输入框输入数据,点击按钮 (2).常见的DOM事件 onload 浏览器已完成页面的加载 ...
- css摩天轮
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8 ...
- Nodejs 创建web服务 静态资源服务器 get数据获取 post数据获取
NodeJs是通过官方提供的http模块来创建 web服务器的模块.通过几行简单的代码,就能轻松的手写一个web服务,从而对外提供 web 服务. ² 创建web服务基本步骤 ①.导入http模块 c ...
- 美团面试:说说Netty的零拷贝技术?
零拷贝技术(Zero-Copy)是一个大家耳熟能详的技术名词了,它主要用于提升 IO(Input & Output)的传输性能. 那么问题来了,为什么零拷贝技术能提升 IO 性能? 1.零拷贝 ...