经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库/运维 » Redis » 查看文章
接口防刷!利用redisson快速实现自定义限流注解
来源:cnblogs  作者:二价亚铁  时间:2024/7/19 10:38:36  对本文有异议

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

如登录接口,当用户使用手机号+验证码登录时,一般我们会生成6位数的随机验证码,并将验证码有效期设置为1-3分钟,如果对登录接口不加以限制,理论上,通过技术手段,快速重试100000次,即可将验证码穷举出来。

解决思路:对登录接口加上限流操作,如限制一分钟内最多登录5次,登录次数过多,就返回失败提示,或者将账号锁定一段时间。

实现手段:利用redis的有序集合即Sorted Set数据结构,构造一个令牌桶来实施限流。而redisson已经帮我们封装成了RRateLimiter,通过redisson,即可快速实现我们的目标。

  • 定义一个限流注解

    1. import org.redisson.api.RateIntervalUnit;
    2. import java.lang.annotation.ElementType;
    3. import java.lang.annotation.Retention;
    4. import java.lang.annotation.RetentionPolicy;
    5. import java.lang.annotation.Target;
    6. @Target(ElementType.METHOD)
    7. @Retention(RetentionPolicy.RUNTIME)
    8. public @interface GlobalRateLimiter {
    9. String key();
    10. long rate();
    11. long rateInterval() default 1L;
    12. RateIntervalUnit rateIntervalUnit() default RateIntervalUnit.SECONDS;
    13. }
  • 利用aop进行切面

    1. import com.zj.demoshow.annotion.GlobalRateLimiter;
    2. import lombok.extern.slf4j.Slf4j;
    3. import org.aspectj.lang.ProceedingJoinPoint;
    4. import org.aspectj.lang.annotation.Around;
    5. import org.aspectj.lang.annotation.Aspect;
    6. import org.aspectj.lang.annotation.Pointcut;
    7. import org.aspectj.lang.reflect.MethodSignature;
    8. import org.redisson.Redisson;
    9. import org.redisson.api.RRateLimiter;
    10. import org.redisson.api.RateIntervalUnit;
    11. import org.redisson.api.RateType;
    12. import org.springframework.beans.factory.annotation.Value;
    13. import org.springframework.core.DefaultParameterNameDiscoverer;
    14. import org.springframework.expression.Expression;
    15. import org.springframework.expression.ExpressionParser;
    16. import org.springframework.expression.spel.standard.SpelExpressionParser;
    17. import org.springframework.expression.spel.support.StandardEvaluationContext;
    18. import org.springframework.stereotype.Component;
    19. import javax.annotation.Resource;
    20. import java.lang.reflect.Method;
    21. import java.util.concurrent.TimeUnit;
    22. @Aspect
    23. @Component
    24. @Slf4j
    25. public class GlobalRateLimiterAspect {
    26. @Resource
    27. private Redisson redisson;
    28. @Value("${spring.application.name}")
    29. private String applicationName;
    30. private final DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
    31. @Pointcut(value = "@annotation(com.zj.demoshow.annotion.GlobalRateLimiter)")
    32. public void cut() {
    33. }
    34. @Around(value = "cut()")
    35. public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    36. MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    37. Method method = methodSignature.getMethod();
    38. String className = method.getDeclaringClass().getName();
    39. String methodName = method.getName();
    40. GlobalRateLimiter globalRateLimiter = method.getDeclaredAnnotation(GlobalRateLimiter.class);
    41. Object[] params = joinPoint.getArgs();
    42. long rate = globalRateLimiter.rate();
    43. String key = globalRateLimiter.key();
    44. long rateInterval = globalRateLimiter.rateInterval();
    45. RateIntervalUnit rateIntervalUnit = globalRateLimiter.rateIntervalUnit();
    46. if (key.contains("#")) {
    47. ExpressionParser parser = new SpelExpressionParser();
    48. StandardEvaluationContext ctx = new StandardEvaluationContext();
    49. String[] parameterNames = discoverer.getParameterNames(method);
    50. if (parameterNames != null) {
    51. for (int i = 0; i < parameterNames.length; i++) {
    52. ctx.setVariable(parameterNames[i], params[i]);
    53. }
    54. }
    55. Expression expression = parser.parseExpression(key);
    56. Object value = expression.getValue(ctx);
    57. if (value == null) {
    58. throw new RuntimeException("key无效");
    59. }
    60. key = value.toString();
    61. }
    62. key = applicationName + "_" + className + "_" + methodName + "_" + key;
    63. log.info("设置限流锁key={}", key);
    64. RRateLimiter rateLimiter = this.redisson.getRateLimiter(key);
    65. if (!rateLimiter.isExists()) {
    66. log.info("设置流量,rate={},rateInterval={},rateIntervalUnit={}", rate, rateInterval, rateIntervalUnit);
    67. rateLimiter.trySetRate(RateType.OVERALL, rate, rateInterval, rateIntervalUnit);
    68. //设置一个过期时间,避免key一直存在浪费内存,这里设置为延长5分钟
    69. long millis = rateIntervalUnit.toMillis(rateInterval);
    70. this.redisson.getBucket(key).expire(Long.sum(5 * 1000 * 60, millis), TimeUnit.MILLISECONDS);
    71. }
    72. boolean acquire = rateLimiter.tryAcquire(1);
    73. if (!acquire) {
    74. //这里直接抛出了异常 也可以抛出自定义异常,通过全局异常处理器拦截进行一些其他逻辑的处理
    75. throw new RuntimeException("请求频率过高,此操作已被限制");
    76. }
    77. return joinPoint.proceed();
    78. }
    79. }

ok,通过以上两步,即可完成我们的限流注解了,下面通过一个接口验证下效果。

新建一个controller,写一个模拟登录的方法。

  1. @RestController
  2. @RequestMapping(value = "/user")
  3. public class UserController {
  4. @PostMapping(value = "/testForLogin")
  5. //以account为锁的key,限制每分钟最多登录5次
  6. @GlobalRateLimiter(key = "#params.account", rate = 5, rateInterval = 60)
  7. R<Object> testForLogin(@RequestBody @Validated LoginParams params) {
  8. //登录逻辑
  9. return R.success("登录成功");
  10. }
  11. }

启动服务,通过postman访问此接口进行验证。
image

原文链接:https://www.cnblogs.com/xw-01/p/18310358

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号