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

  首先定义注解AccessFrequencyLimiter,注解包含四个参数,限制一段时间内同一IP地址最多访问接口次数,以及报错信息和报错之后再次可以访问接口的时间间隔。

  1. @Target(ElementType.METHOD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @interface AccessFrequencyLimiter {
  5.  
  6. /**
  7. * 从第一次访问接口的时间到cycle周期时间内,无法超过frequency次
  8. */
  9. int frequency() default 5;
  10.  
  11. /**
  12. * 周期时间,单位ms:
  13. * 默认周期时间为一分钟
  14. */
  15. long cycle() default 60 * 1000;
  16.  
  17. /**
  18. * 返回的错误信息
  19. */
  20. String message() default "操作过于频繁,请稍后再试";
  21.  
  22. /**
  23. * key到期时间,单位s:
  24. * 如果在cycle周期时间内超过frequency次,则默认5分钟内无法继续访问
  25. */
  26. long expireTime() default 5 * 60;
  27. }

  利用AOP,实现防刷逻辑。具体代码如下,通过Redis保存某个IP首次访问接口的时间,和访问次数,然后在限制时间内对访问次数进行累加,超过最大次数则抛出操作太频繁的异常,需要等待Redis的key过期之后才能再次访问该接口,达到接口防刷的效果。

  1. @Aspect
  2. @Component
  3. public class AccessFrequencyLimitingAspect {
  4. private static final String LIMITING_KEY = "limiting:%s:%s";
  5. private static final String LIMITING_BEGINTIME = "beginTime";
  6. private static final String LIMITING_EXFREQUENCY = "exFrequency";
  7.  
  8. @Autowired
  9. private RedisTemplate redisTemplate;
  10.  
  11. @Pointcut("@annotation(accessFrequencyLimiter)")
  12. public void pointcut(AccessFrequencyLimiter accessFrequencyLimiter) {
  13. }
  14.  
  15. @Around("pointcut(accessFrequencyLimiter)")
  16. public Object around(ProceedingJoinPoint pjp, AccessFrequencyLimiter accessFrequencyLimiter) throws Throwable {
  17. //获取请求的ip和方法
  18. String ipAddress = WebUtil.getIpAddress();
  19. String methodName = pjp.getSignature().toLongString();
  20.  
  21. //获取redis中周期内第一次访问方法的时间和已访问过接口的次数
  22. Long beginTimeLong = (Long) redisTemplate.opsForHash().get(String.format(LIMITING_KEY, ipAddress, methodName), LIMITING_BEGINTIME);
  23. Integer exFrequencyLong = (Integer) redisTemplate.opsForHash().get(String.format(LIMITING_KEY, ipAddress, methodName), LIMITING_EXFREQUENCY);
  24. long beginTime = beginTimeLong == null ? 0L : beginTimeLong;
  25. int exFrequency = exFrequencyLong == null ? 0 : exFrequencyLong;
  26.  
  27. //当两次访问时间差超过限制时间时,记录最新访问时间作为一个访问周期内的首次访问时间,并设置访问次数为1
  28. if (System.currentTimeMillis() - beginTime > accessFrequencyLimiter.cycle()) {
  29. redisTemplate.opsForHash().put(String.format(LIMITING_KEY, ipAddress, methodName), LIMITING_BEGINTIME, System.currentTimeMillis());
  30. redisTemplate.opsForHash().put(String.format(LIMITING_KEY, ipAddress, methodName), LIMITING_EXFREQUENCY, 1);
  31. //设置key的过期时间
  32. redisTemplate.expire(String.format(LIMITING_KEY, ipAddress, methodName), accessFrequencyLimiter.expireTime(), TimeUnit.SECONDS);
  33. return pjp.proceed();
  34. } else {
  35. //如果该次访问与首次访问时间差在限制时间段内,则访问次数+1,并刷新key的过期时间
  36. if (exFrequency < accessFrequencyLimiter.frequency()) {
  37. redisTemplate.opsForHash().put(String.format(LIMITING_KEY, ipAddress, methodName), LIMITING_EXFREQUENCY, exFrequency + 1);
  38. redisTemplate.expire(String.format(LIMITING_KEY, ipAddress, methodName), accessFrequencyLimiter.expireTime(), TimeUnit.SECONDS);
  39. return pjp.proceed();
  40. } else {
  41. //限制时间内访问次数超过最大可访问次数,抛出异常
  42. throw new ServiceException(accessFrequencyLimiter.message());
  43. }
  44. }
  45. }
  46. }

  获取访问接口的IP地址,工具类WebUtil代码如下:

  1. public class WebUtil {
  2.  
  3. private static final String UNKNOWN = "unknown";
  4.  
  5. //获取request
  6. public static HttpServletRequest getRequest() {
  7. return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
  8. }
  9.  
  10. //获取response
  11. public static HttpServletResponse getResponse() {
  12. return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
  13. }
  14.  
  15. public static String getIpAddress() {
  16. HttpServletRequest request = getRequest();
  17. String ip = request.getHeader("x-forwarded-for");
  18. if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
  19. ip = request.getHeader("Proxy-Client-IP");
  20. }
  21.  
  22. if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
  23. ip = request.getHeader("WL-Proxy-Client-IP");
  24. }
  25.  
  26. if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
  27. ip = request.getRemoteAddr();
  28. }
  29.  
  30. if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
  31. ip = request.getHeader("HTTP_CLIENT_IP");
  32. }
  33.  
  34. if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
  35. ip = request.getHeader("X-Real-IP");
  36. }
  37.  
  38. if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
  39. ip = request.getHeader("HTTP_X_FORWARDED_FOR");
  40. }
  41.  
  42. String regex = ",";
  43. if (ip != null && ip.indexOf(regex) > 0) {
  44. ip = ip.split(regex)[0];
  45. }
  46.  
  47. return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
  48. }
  49.  
  50. }

  在接口上添加该注解进行测试,自定义接口限制时间内访问次数,以及报错信息等参数,代码如下:

  1. @AccessFrequencyLimiter(frequency = 3, cycle = 5 * 60 * 1000, message = "操作过于频繁,请五分钟之后再试")
  2. @ApiOperation("发送验证码,同一个ip五分钟内最多只能发送三次验证码,超过次数即提示“操作过于频繁,请五分钟之后再试")
  3. @GetMapping("insurance_policy/unbind_verification_code")
  4. Response<Boolean> sendUnbindVerificationCode(@RequestParam(name = "mcard_no") String mcardNo) {return Response.success;}

  测试结果如下:

使用Redis+自定义注解实现接口防刷的更多相关文章

  1. Spring Boot项目的接口防刷

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

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

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

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

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

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

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

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

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

  6. spring中实现基于注解实现动态的接口限流防刷

    本文将介绍在spring项目中自定义注解,借助redis实现接口的限流 自定义注解类 import java.lang.annotation.ElementType; import java.lang ...

  7. 服务限流 -- 自定义注解基于RateLimiter实现接口限流

    1. 令牌桶限流算法 令牌桶会以一个恒定的速率向固定容量大小桶中放入令牌,当有浏览来时取走一个或者多个令牌,当发生高并发情况下拿到令牌的执行业务逻辑,没有获取到令牌的就会丢弃获取服务降级处理,提示一个 ...

  8. 使用AOP+自定义注解完成spring boot的接口权限校验

    记使用AOP+自定义注解完成接口的权限校验,代码如下: pom文件添加所需依赖: 1 <dependency> 2 <groupId>org.aspectj</group ...

  9. spring boot集成swagger,自定义注解,拦截器,xss过滤,异步调用,guava限流,定时任务案例, 发邮件

    本文介绍spring boot集成swagger,自定义注解,拦截器,xss过滤,异步调用,定时任务案例 集成swagger--对于做前后端分离的项目,后端只需要提供接口访问,swagger提供了接口 ...

随机推荐

  1. 9.2 k8s结合Jenkins与gitlab实现代码升级与回滚

    1.部署Jenkins 1.1 安装jdk # apt 安装jdk11 apt install openjdk-11-jdk # 查看 root@jenkins:~# java -version op ...

  2. Java 中的监控与管理原理概述

    点赞再看,动力无限.Hello world : ) 微信搜「程序猿阿朗 」. 本文 Github.com/niumoo/JavaNotes 和 程序猿阿朗博客 已经收录,有很多知识点和系列文章. 当前 ...

  3. vue3 高阶 API 大汇总,强到离谱

    高阶函数是什么呢? 高阶函数英文名叫:Higher Order function ,一个函数可以接收一个或多个函数作为输入,或者输出一个函数,至少满足上述条件之一的函数,叫做高阶函数. 前言 本篇内容 ...

  4. 生成&添加 SSH公钥

    生成&添加 SSH公钥 生成 打开 Terminal(终端) 生成命令 ssh-keygen -t ed25519 -C "your_email@example.com" ...

  5. 系统发育树邻接法(NJ)和非加权组平均法(UPGMA)之比较

    目录 1.原理的区别 2.实操比较 UPGMA NJ法 保存树文件 更深理解 1.原理的区别 主要区别在于,非加权组平均法(UPGMA)是基于平均链接方法的聚集层次聚类方法,而邻接法(NJ)是基于最小 ...

  6. R语言与医学统计图形-【17】ggplot2几何对象之热图

    ggplot2绘图系统--heatmap.geom_rect 这里不介绍更常见的pheatmap包. 1.heatmap函数 基础包. data=as.matrix(mtcars) #接受矩阵 hea ...

  7. Linux运维工程师面试题整理

    1. Nginx 反向代理,负载均衡,动静分离,工作原理及优化nginx配置反向代理. vim Nginx.confServer模块中配置Listen 80Server_name ip;在server ...

  8. Python基础之字典内置方法

    目录 1. 字典 1.1 字典的作用 1.2 创建和使用字典 1.2.1 dict类 1.2.2 基本的字典操作 1.2.3 字典方法 1. 字典 映射:可以通过名称来访问其各个值的数据结构. 字典是 ...

  9. php背景透明png

    php背景透明png php处理图片时,例如生成水印,对于png的水印经常背景会加有色的背景,用此方法可以去除背景 主要函数:imagecolortransparent: //添加水印 $src = ...

  10. day06 HTTP协议

    day06 HTTP协议 HTTP协议 什么是http? HTTP 全称:Hyper Text Transfer Protocol 中文名:超文本传输协议 是一种按照URL指示,将超文本文档从一台主机 ...