问题:

在日常开发中,一些重要的对外接口,需要加上访问频率限制,以免造成资��损失。

如登录接口,当用户使用手机号+验证码登录时,一般我们会生成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快速实现自定义限流注解的更多相关文章

  1. 使用Redis+自定义注解实现接口防刷

    最近开发了一个功能,需要发送短信验证码鉴权,考虑到短信服务需要收费,因此对此接口做了防刷处理,实现方式主要是Redis+自定义注解(需要导入Redis的相关依赖,完成Redis的相关配置,gs代码,这 ...

  2. Spring Boot项目的接口防刷

    说明:使用了注解的方式进行对接口防刷的功能,非常高大上,本文章仅供参考 一,技术要点:springboot的基本知识,redis基本操作, 首先是写一个注解类: import java.lang.an ...

  3. Spring Boot 项目的 API 接口防刷

    首先是写一个注解类 拦截器中实现 注册到springboot中 在Controller中加入注解 说明:使用了注解的方式进行对接口防刷的功能,非常高大上,本文章仅供参考 一,技术要点:springbo ...

  4. [转]Springboot项目的接口防刷的实例

    来源:微信公众号 马士兵 原地址:https://mp.weixin.qq.com/s/tHQcWwIt4c41IUnvCQ2QWA 说明:使用了注解的方式进行对接口防刷的功能,非常高大上,本文章仅供 ...

  5. SpringCloud(六)之 网关概念、Zuul项目搭建-(利用Zuul 实现鉴权和限流实战)

    一.网关概念 1.什么是路由网关 网关是系统的唯一对外的入口,介于客户端和服务器端之间的中间层,处理非业务功能 提供路由请求.鉴权.监控.缓存.限流等功能.它将"1对N"问题转换成 ...

  6. spring boot gateway自定义限流

    参考:https://blog.csdn.net/ErickPang/article/details/84680132 采用自带默认网关请参照微服务架构spring cloud - gateway网关 ...

  7. 页面接口防刷 解决思路一nginx

    线上环境 很多接口 如果不做缓存 可能导致有人拿到url  每秒几万次的访问后台程序,导致系统down机. 此处, nginx可以加一层缓存. expires起到控制页面缓存的作用,合理的配置expi ...

  8. SpringBoot接口防刷

    一.自定义注解 import java.lang.annotation.Retention; import java.lang.annotation.Target; import static jav ...

  9. Springboot项目的接口防刷(实例)

    技术要点:springboot的基本知识,redis基本操作, 首先是写一个注解类: import java.lang.annotation.Retention; import java.lang.a ...

  10. 不死的小强 .net core 微服务 快速开发框架 Viper 限流

    1.Viper是什么? Viper 是.NET平台下的Anno微服务框架的一个示例项目.入门简单.安全.稳定.高可用.全平台可监控.底层通讯可以随意切换thrift grpc. 自带服务发现.调用链追 ...

随机推荐

  1. Machine Learning - 笔记1

    一.监督学习(supervised learning) 1.回归(regression)         ①按我的理解来说,就是对于训练模型所用到的数据集,能够知道Input和Output的特征.比如 ...

  2. winform——windows 服务的安装 卸载 启动 停止

    安装服务和卸载服务采用process启动命令行的方式.启动服务和停止服务用到了 System.ServiceProcess.ServiceController这个类.   程序是这样运行的,首先启动服 ...

  3. java练习项目——记账本

    包含登录.注册.记账.每日账单查看.每月报表.添加记账类型这些功能.数据存储采用的是txt文档+xml文档.程序是一个控制台程序,用IntelliJ IDEA+jdk8开发.涉及到的知识有List集合 ...

  4. Nginx的正向代理和反向代理

    1.Nginx代理 (1)Nginx出现的早期就是作为一个高性能的http.反向代理的web服务器. (2)同时它也提供了IMAP/POP3/SMTP服务,也就是电子邮件代理服务器. (3)它的特点就 ...

  5. .NET桌面程序混合开发之三:WebView2与JS的深度应用

    在 WebView2 控件中使用 JavaScript 根据需求自由扩展原生应用的能力.本文探讨如何在 WebView2 中使用 JavaScript,并列举如何使用高级 WebView2 特性和功能 ...

  6. firewall防火墙基础配置

    Firewalld防火墙 Centos7 中集成了防火墙管理工具, Firewall系统动态防火墙管理器是作为默认的防火墙工具. 它支持规则动态更新,并加入zone区域概念. Firewalld防火墙 ...

  7. 修改linux默认启动界面——从命令行模式转换为图形化模式

    从命令行模式转换为图形化模式 首先需要安装对应的图形化安装包 yum groupinstall "GNOME Desktop" "Graphical Administra ...

  8. FreeRTOS-02-列表和列表项

    说明: 本文仅作为学习FreeRTOS的记录文档,作为初学者肯定很多理解不对甚至错误的地方,望网友指正. FreeRTOS是一个RTOS(实时操作系统)系统,支持抢占式.合作式和时间片调度.适用于微处 ...

  9. Linux进程间通信-FIFO(命名管道)

    本系列文章主要是学习记录Linux下进程间通信的方式. 常用的进程间通信方式:管道.FIFO.消息队列.信号量以及共享存储. 参考文档:<UNIX环境高级编程(第三版)> 参考视频:Lin ...

  10. (八十八)c#Winform自定义控件-转子

    官网 http://www.hzhcontrols.com/ 前提 入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章. GitHub:https://github.com/kw ...