问题:

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

如登录接口,当用户使用手机号+验证码登录时,一般我们会生成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. Django——admin后台上传文件

    from django.db import models class Mytb(models.Model): file = models.FileField(upload_to='uploads/') ...

  2. 一个与 WSL2 建立远程的简单方法

    前言 众所周知,windows 会通过虚拟交换机给本机和 wsl2(Linux 子系统)分别分配 ip.于是本机重启或重启 wsl 服务的时候会重新分配 ip.之前所作的端口转发,监听之类的都会失效. ...

  3. 【阿里天池云-龙珠计划】薄书的机器学习笔记——K近邻(k-nearest neighbors)初探Task02

    [阿里天池云-龙珠计划]薄书的机器学习笔记--K近邻(k-nearest neighbors)初探Task02 [给各位看官请安] 大家一起来集齐七龙珠召唤神龙吧!!! 学习地址:AI训练营机器学习- ...

  4. 【论文笔记】YOLO系列

    [深度学习]总目录 YOLOv1:<You Only Look Once: Unified, Real-Time Object Detection>one-stage的开山之作,将目标检测 ...

  5. c语言不可不说的一件事——进制之间的转化

    进制之间的转换 二进制转换 二进制转换为十进制 10111010.101 转换方法: 个位数是乘以2的0次方 从小数点开始分割,(小数点前从右往左,小数点后从左往右)乘以2的对应次方(从0开始取),最 ...

  6. jquery浏览器的上卷高度 节点的创建和写入 节点的删除

    // js 的兼容语法         // let scrollT = document.documentElement.scrollTop || document.body.scrollTop; ...

  7. CF1753

    CF1753 成功因为虚拟机炸了,重新写一遍此文. 都是没有保存的错. A. Make Nonzero Sum 由于 Note that it is not required to minimize ...

  8. C#.NET 使用HttpWebRequest发送JSON

    方法: public static string HttpPostJson(string url, string postStr, int timeOut, string charset) { Htt ...

  9. Unity UI优化

    UI优化 动静分离.拆分UI.预加载.字体拆分.滚屏优化.网格重构优化.展示关闭优化.对象池.贴图优化.图集拼接优化.UI业务逻辑中GC优化等. 一.动静分离 ** 问题:**unity中UGUI系统 ...

  10. CAP 8.2 版本发布通告

    前言 今天我们很高兴宣布 CAP 发布 8.2 版本正式版,我们在这个版本中主要致力于对订阅着并行执行的特性提供支持,同时添加了对在订阅者中对消息头的控制行为. 下面,具体看一下我们新版本的功能吧. ...