Spring自定义注解+redis实现接口限流
在实际开发中,有时候我们需要对某些接口进行限流,防止有人恶意攻击或者是因为某些接口自身的原因,比如发短信接口,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实现接口限流相关推荐
- redis rua解决库存问题_库存秒杀问题-redis解决方案- 接口限流
/** * Created by PhpStorm. * redis 销量超卖秒杀解决方案 * redis 文档:http://doc.redisfans.com/ * ab -n 10000 -c ...
- Redis 做接口限流
Redis 除了做缓存,还能干很多很多事情:分布式锁.限流.处理请求接口幂等性...太多太多了- 今天想和小伙伴们聊聊用 Redis 处理接口限流,这也是最近的 TienChin 项目涉及到这个知识点 ...
- Redis做接口限流
Redis 除了做缓存,还能干很多很多事情:分布式锁.限流.处理请求接口幂等性...太多太多了-,今天想和小伙伴们聊聊用 Redis 处理接口限流. 1. 准备工作 首先我们创建一个 Spring B ...
- Spring Cloud Gateway 原生的接口限流该怎么玩
为什么80%的码农都做不了架构师?>>> 关于pig: 基于Spring Cloud.oAuth2.0开发基于Vue前后分离的开发平台,支持账号.短信.SSO等多种登录,提供配 ...
- Spring Cloud Gateway 原生支持接口限流该怎么玩
关于pig: 基于Spring Cloud.oAuth2.0开发基于Vue前后分离的开发平台,支持账号.短信.SSO等多种登录,提供配套视频开发教程. 关于 Spring Cloud Gateway ...
- 5.概念(maven,ssm,springMvc,spring,自定义注解,二级缓存,范式,事务,mysql,线程池,map,hashmap,redis,饿汉,懒汉)
maven是啥: 1.Maven是一个项目管理和综合工具.Maven提供了开发人员构建一个完整的生命周期框架. 创建-导入jar报–编写配置文件-实现业务功能-测试-发布上线. 2.开发团队可以自动完 ...
- spring boot + redis 实现网站限流和接口防刷功能
源码url: https://github.com/zhzhair/accesslimit-spring-boot.git 注解@AccessLimit 实现接口防刷功能,在方法上的注解参数优先于类上 ...
- Spring Cloud Alibaba基础教程:使用Sentinel实现接口限流
点击蓝色"程序猿DD"关注我哟 加个"星标",不忘签到哦 最近管点闲事浪费了不少时间,感谢网友们的留言提醒. 及时纠正路线,继续跟大家一起学习Spring Cl ...
- Gateway配合sentinel自定义限流_你知道如何使用阿里Sentinel实现接口限流吗?
Nacos作为注册中心和配置中心的基础教程,到这里先告一段落,后续与其他结合的内容等讲到的时候再一起拿出来说,不然内容会有点跳跃.接下来我们就来一起学习一下Spring Cloud Alibaba下的 ...
最新文章
- Spring boot显示登录用户
- 运维中心建设--服务台建设一期
- 流行病学与生物统计学: 临床研究导论 Epidemiology and Biostatistics: An Introduction to Clinical Research
- jdk w7环境变量配置
- 面试开发可以用python_Python开发工程师面试题(五)
- 调用 oauth2_奥利给,再也不怕面试官问我OAuth 2.0授权了
- python高斯求和_利用Python进行数据分析(3)- 列表、元组、字典、集合
- qt禁止拖动_[Qt]QMdiArea,无框架窗口的拖动
- ANN 神经网络介绍
- CSUOJ 1197- Staginner 买葡萄
- k2p华硕系统怎么设置_双频路由器怎么设置网速快(k2p刷华硕怎么设置网速好)
- 手游服务器开发技术详解
- Xcode 设置macos app的适配系统,显示禁止图标
- JavaScript中的数据类型判断
- RHCE考试——佩琦
- 求任何时间下不同纬度太阳高度角的计算公式
- 图文详解:K8S太火了!花10分钟玩转它不香么
- Expanding Low-Density Latent Regions for Open-Set Object Detection
- android书籍推荐!分析Android未来几年的发展前景,灵魂拷问
- dubbo学习笔记 一 源码编译
热门文章
- Python 打包 exe 程序避坑指南:没有安装包也能运行小程序啦~开心
- 解决Ubuntu与Windows之间无法复制粘贴问题
- [ATF]-MTK:一篇文章了解ATF原理
- [Zer0pts2020]ROR
- TCP/IP的基本介绍
- VMProtect SDK+ASProtect SDK保护
- (36)内核空间与内核模块,遍历内核模块链表
- 管理员请注意 一条后门病毒攻击链正在针对服务器发起入侵
- 逆向去除winrar广告
- Codeforces Beta Round #9 (Div. 2 Only)【未完结】