在实际开发中,有时候我们需要对某些接口进行限流,防止有人恶意攻击或者是因为某些接口自身的原因,比如发短信接口,IO处理的接口。

这里我们通过自定义一个注解,并利用Spring的AOP拦截器功能来实现限流的功能。限流需要用到redis。

代码:

Limit.java

这里我们有两种限流类型,一种是根据接口本身来进行限流,一种是根据ip来进行限流

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Limit {
​// 资源名称,用于描述接口功能String name() default "";
​// 资源 keyString key() default "";
​// key prefixString prefix() default "";
​// 时间的,单位秒int period() default 60;
​// 限制访问次数int count() default 60;
​// 限制类型LimitType limitType() default LimitType.IP;
​
​/*** 使用实例:* 测试限流注解,下面配置说明该接口 60秒内最多只能访问 10次,保存到redis的键名为 limit_test,* 即 prefix + "_" + key,也可以根据 IP 来限流,需指定limitType = LimitType.IP*/
//    @Limit(key = "test", period = 60, count = 10, name = "resource", prefix = "limit")
//    @GetMapping("/test")
//    public int testLimiter() {
//        return ATOMIC_INTEGER.incrementAndGet();
//    }
​
}

LimitAspect.java

@Aspect
@Component
public class LimitAspect {
​private static final Logger logger = LoggerFactory.getLogger(LimitAspect.class);
​@Autowiredprivate RedisTemplate<String, Object> limitRedisTemplate;
​
​@Pointcut("@annotation(com.yfy.annotation.Limit)")public void pointcut() {// do nothing}
​@Around("pointcut()")public Object around(ProceedingJoinPoint point) throws Throwable {HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
​MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();Limit limitAnnotation = method.getAnnotation(Limit.class);LimitType limitType = limitAnnotation.limitType();String name = limitAnnotation.name();String key;int limitPeriod = limitAnnotation.period();int limitCount = limitAnnotation.count();switch (limitType) {case IP:key = IPUtils.getIpAddr(request);break;case CUSTOMER:key = limitAnnotation.key();break;default:key = StringUtils.upperCase(method.getName());}ImmutableList<String> keys = ImmutableList.of(StringUtils.join(limitAnnotation.prefix() + "_", key + "_" + request.getRequestedSessionId()));String luaScript = buildLuaScript();RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class);Number count = limitRedisTemplate.execute(redisScript, keys, limitCount, limitPeriod);logger.info("第{}次访问key为 {},描述为 [{}] 的接口", count, keys, name);if (count != null && count.intValue() <= limitCount) {return point.proceed();} else {throw new LimitAccessException("接口访问超出频率限制");}
​}
​/*** 限流脚本* 调用的时候不超过阈值,则直接返回并执行计算器自加。** @return lua脚本*/private String buildLuaScript() {return "local c" +"\nc = redis.call('get',KEYS[1])" +"\nif c and tonumber(c) > tonumber(ARGV[1]) then" +"\nreturn c;" +"\nend" +"\nc = redis.call('incr',KEYS[1])" +"\nif tonumber(c) == 1 then" +"\nredis.call('expire',KEYS[1],ARGV[2])" +"\nend" +"\nreturn c;";}
​
}

IPUtils.java

public class IPUtils {
​private static final String UNKNOWN = "unknown";
​protected IPUtils(){
​}
​/*** 获取IP地址* 使用 Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址* 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非 unknown的有效IP字符串,则为真实IP地址*/public static String getIpAddr(HttpServletRequest request) {String ip = request.getHeader("x-forwarded-for");if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;}
​
}

Report.java

@Data
public class Report  implements Serializable {
​private Integer status;private Object data;private Object notice;private Object msg;
}

ExceptionTemplet.java

public interface ExceptionTemplet {Report report();
}

BaseServiceException.java

public abstract class BaseServiceException extends RuntimeException implements ExceptionTemplet {private Report report;public BaseServiceException(String message) {super(message);}public BaseServiceException report(Report report){this.report = report;return this;};
​@Overridepublic Report report() {return report;}
}

LimitAccessException.java

public class LimitAccessException extends BaseServiceException {
​private static final long serialVersionUID = -3608667856397125671L;
​public LimitAccessException(String message) {super(message);}@Overridepublic Report report() {return ReportFactory.C_1404_CLIENT_REQUEST_DATAERROR.error(getMessage());}
}

测试:

我们写一个controller接口

    @GetMapping("/limit")@ResponseBody@Limit(key = "test", period = 60, count = 10, name = "resource", prefix = "limit")public String testLimit() {return "success";}

该接口中的注解表明该接口在60秒内,同一个ip地址最多只能访问10次,如果访问超过10次,则抛出异常。

对于该异常,如果我们希望给用户友好的提示,可以利用Spring的全局异常处理类来对异常进行特殊处理。

Report类为前后端分离中与前端自定义的返回值类

ControllerExceptionAdvice.java

@RestControllerAdvice
public class ControllerExceptionAdvice {private Logger logger = LoggerFactory.getLogger(ControllerExceptionAdvice.class);
​@ExceptionHandler(value = LimitAccessException.class)@ResponseBodypublic Report handle(LimitAccessException e) {return e.report();}
}

Spring自定义注解+redis实现接口限流相关推荐

  1. redis rua解决库存问题_库存秒杀问题-redis解决方案- 接口限流

    /** * Created by PhpStorm. * redis 销量超卖秒杀解决方案 * redis 文档:http://doc.redisfans.com/ * ab -n 10000 -c ...

  2. Redis 做接口限流

    Redis 除了做缓存,还能干很多很多事情:分布式锁.限流.处理请求接口幂等性...太多太多了- 今天想和小伙伴们聊聊用 Redis 处理接口限流,这也是最近的 TienChin 项目涉及到这个知识点 ...

  3. Redis做接口限流

    Redis 除了做缓存,还能干很多很多事情:分布式锁.限流.处理请求接口幂等性...太多太多了-,今天想和小伙伴们聊聊用 Redis 处理接口限流. 1. 准备工作 首先我们创建一个 Spring B ...

  4. Spring Cloud Gateway 原生的接口限流该怎么玩

    为什么80%的码农都做不了架构师?>>>    关于pig: 基于Spring Cloud.oAuth2.0开发基于Vue前后分离的开发平台,支持账号.短信.SSO等多种登录,提供配 ...

  5. Spring Cloud Gateway 原生支持接口限流该怎么玩

    关于pig: 基于Spring Cloud.oAuth2.0开发基于Vue前后分离的开发平台,支持账号.短信.SSO等多种登录,提供配套视频开发教程. 关于 Spring Cloud Gateway ...

  6. 5.概念(maven,ssm,springMvc,spring,自定义注解,二级缓存,范式,事务,mysql,线程池,map,hashmap,redis,饿汉,懒汉)

    maven是啥: 1.Maven是一个项目管理和综合工具.Maven提供了开发人员构建一个完整的生命周期框架. 创建-导入jar报–编写配置文件-实现业务功能-测试-发布上线. 2.开发团队可以自动完 ...

  7. spring boot + redis 实现网站限流和接口防刷功能

    源码url: https://github.com/zhzhair/accesslimit-spring-boot.git 注解@AccessLimit 实现接口防刷功能,在方法上的注解参数优先于类上 ...

  8. Spring Cloud Alibaba基础教程:使用Sentinel实现接口限流

    点击蓝色"程序猿DD"关注我哟 加个"星标",不忘签到哦 最近管点闲事浪费了不少时间,感谢网友们的留言提醒. 及时纠正路线,继续跟大家一起学习Spring Cl ...

  9. Gateway配合sentinel自定义限流_你知道如何使用阿里Sentinel实现接口限流吗?

    Nacos作为注册中心和配置中心的基础教程,到这里先告一段落,后续与其他结合的内容等讲到的时候再一起拿出来说,不然内容会有点跳跃.接下来我们就来一起学习一下Spring Cloud Alibaba下的 ...

最新文章

  1. Spring boot显示登录用户
  2. 运维中心建设--服务台建设一期
  3. 流行病学与生物统计学: 临床研究导论 Epidemiology and Biostatistics: An Introduction to Clinical Research
  4. jdk w7环境变量配置
  5. 面试开发可以用python_Python开发工程师面试题(五)
  6. 调用 oauth2_奥利给,再也不怕面试官问我OAuth 2.0授权了
  7. python高斯求和_利用Python进行数据分析(3)- 列表、元组、字典、集合
  8. qt禁止拖动_[Qt]QMdiArea,无框架窗口的拖动
  9. ANN 神经网络介绍
  10. CSUOJ 1197- Staginner 买葡萄
  11. k2p华硕系统怎么设置_双频路由器怎么设置网速快(k2p刷华硕怎么设置网速好)
  12. 手游服务器开发技术详解
  13. Xcode 设置macos app的适配系统,显示禁止图标
  14. JavaScript中的数据类型判断
  15. RHCE考试——佩琦
  16. 求任何时间下不同纬度太阳高度角的计算公式
  17. 图文详解:K8S太火了!花10分钟玩转它不香么
  18. Expanding Low-Density Latent Regions for Open-Set Object Detection
  19. android书籍推荐!分析Android未来几年的发展前景,灵魂拷问
  20. dubbo学习笔记 一 源码编译

热门文章

  1. Python 打包 exe 程序避坑指南:没有安装包也能运行小程序啦~开心
  2. 解决Ubuntu与Windows之间无法复制粘贴问题
  3. [ATF]-MTK:一篇文章了解ATF原理
  4. [Zer0pts2020]ROR
  5. TCP/IP的基本介绍
  6. VMProtect SDK+ASProtect SDK保护
  7. (36)内核空间与内核模块,遍历内核模块链表
  8. 管理员请注意 一条后门病毒攻击链正在针对服务器发起入侵
  9. 逆向去除winrar广告
  10. Codeforces Beta Round #9 (Div. 2 Only)【未完结】