使用Redis+自定义注解实现接口防刷
最近开发了一个功能,需要发送短信验证码鉴权,考虑到短信服务需要收费,因此对此接口做了防刷处理,实现方式主要是Redis+自定义注解(需要导入Redis的相关依赖,完成Redis的相关配置,gs代码,这里不做展示)。
首先定义注解AccessFrequencyLimiter,注解包含四个参数,限制一段时间内同一IP地址最多访问接口次数,以及报错信息和报错之后再次可以访问接口的时间间隔。
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface AccessFrequencyLimiter {
- /**
- * 从第一次访问接口的时间到cycle周期时间内,无法超过frequency次
- */
- int frequency() default 5;
- /**
- * 周期时间,单位ms:
- * 默认周期时间为一分钟
- */
- long cycle() default 60 * 1000;
- /**
- * 返回的错误信息
- */
- String message() default "操作过于频繁,请稍后再试";
- /**
- * key到期时间,单位s:
- * 如果在cycle周期时间内超过frequency次,则默认5分钟内无法继续访问
- */
- long expireTime() default 5 * 60;
- }
利用AOP,实现防刷逻辑。具体代码如下,通过Redis保存某个IP首次访问接口的时间,和访问次数,然后在限制时间内对访问次数进行累加,超过最大次数则抛出操作太频繁的异常,需要等待Redis的key过期之后才能再次访问该接口,达到接口防刷的效果。
- @Aspect
- @Component
- public class AccessFrequencyLimitingAspect {
- private static final String LIMITING_KEY = "limiting:%s:%s";
- private static final String LIMITING_BEGINTIME = "beginTime";
- private static final String LIMITING_EXFREQUENCY = "exFrequency";
- @Autowired
- private RedisTemplate redisTemplate;
- @Pointcut("@annotation(accessFrequencyLimiter)")
- public void pointcut(AccessFrequencyLimiter accessFrequencyLimiter) {
- }
- @Around("pointcut(accessFrequencyLimiter)")
- public Object around(ProceedingJoinPoint pjp, AccessFrequencyLimiter accessFrequencyLimiter) throws Throwable {
- //获取请求的ip和方法
- String ipAddress = WebUtil.getIpAddress();
- String methodName = pjp.getSignature().toLongString();
- //获取redis中周期内第一次访问方法的时间和已访问过接口的次数
- Long beginTimeLong = (Long) redisTemplate.opsForHash().get(String.format(LIMITING_KEY, ipAddress, methodName), LIMITING_BEGINTIME);
- Integer exFrequencyLong = (Integer) redisTemplate.opsForHash().get(String.format(LIMITING_KEY, ipAddress, methodName), LIMITING_EXFREQUENCY);
- long beginTime = beginTimeLong == null ? 0L : beginTimeLong;
- int exFrequency = exFrequencyLong == null ? 0 : exFrequencyLong;
- //当两次访问时间差超过限制时间时,记录最新访问时间作为一个访问周期内的首次访问时间,并设置访问次数为1
- if (System.currentTimeMillis() - beginTime > accessFrequencyLimiter.cycle()) {
- redisTemplate.opsForHash().put(String.format(LIMITING_KEY, ipAddress, methodName), LIMITING_BEGINTIME, System.currentTimeMillis());
- redisTemplate.opsForHash().put(String.format(LIMITING_KEY, ipAddress, methodName), LIMITING_EXFREQUENCY, 1);
- //设置key的过期时间
- redisTemplate.expire(String.format(LIMITING_KEY, ipAddress, methodName), accessFrequencyLimiter.expireTime(), TimeUnit.SECONDS);
- return pjp.proceed();
- } else {
- //如果该次访问与首次访问时间差在限制时间段内,则访问次数+1,并刷新key的过期时间
- if (exFrequency < accessFrequencyLimiter.frequency()) {
- redisTemplate.opsForHash().put(String.format(LIMITING_KEY, ipAddress, methodName), LIMITING_EXFREQUENCY, exFrequency + 1);
- redisTemplate.expire(String.format(LIMITING_KEY, ipAddress, methodName), accessFrequencyLimiter.expireTime(), TimeUnit.SECONDS);
- return pjp.proceed();
- } else {
- //限制时间内访问次数超过最大可访问次数,抛出异常
- throw new ServiceException(accessFrequencyLimiter.message());
- }
- }
- }
- }
获取访问接口的IP地址,工具类WebUtil代码如下:
- public class WebUtil {
- private static final String UNKNOWN = "unknown";
- //获取request
- public static HttpServletRequest getRequest() {
- return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
- }
- //获取response
- public static HttpServletResponse getResponse() {
- return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
- }
- public static String getIpAddress() {
- HttpServletRequest request = getRequest();
- 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();
- }
- if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
- ip = request.getHeader("HTTP_CLIENT_IP");
- }
- if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
- ip = request.getHeader("X-Real-IP");
- }
- if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
- ip = request.getHeader("HTTP_X_FORWARDED_FOR");
- }
- String regex = ",";
- if (ip != null && ip.indexOf(regex) > 0) {
- ip = ip.split(regex)[0];
- }
- return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
- }
- }
在接口上添加该注解进行测试,自定义接口限制时间内访问次数,以及报错信息等参数,代码如下:
- @AccessFrequencyLimiter(frequency = 3, cycle = 5 * 60 * 1000, message = "操作过于频繁,请五分钟之后再试")
- @ApiOperation("发送验证码,同一个ip五分钟内最多只能发送三次验证码,超过次数即提示“操作过于频繁,请五分钟之后再试")
- @GetMapping("insurance_policy/unbind_verification_code")
- Response<Boolean> sendUnbindVerificationCode(@RequestParam(name = "mcard_no") String mcardNo) {return Response.success;}
测试结果如下:
使用Redis+自定义注解实现接口防刷的更多相关文章
- Spring Boot项目的接口防刷
说明:使用了注解的方式进行对接口防刷的功能,非常高大上,本文章仅供参考 一,技术要点:springboot的基本知识,redis基本操作, 首先是写一个注解类: import java.lang.an ...
- Spring Boot 项目的 API 接口防刷
首先是写一个注解类 拦截器中实现 注册到springboot中 在Controller中加入注解 说明:使用了注解的方式进行对接口防刷的功能,非常高大上,本文章仅供参考 一,技术要点:springbo ...
- [转]Springboot项目的接口防刷的实例
来源:微信公众号 马士兵 原地址:https://mp.weixin.qq.com/s/tHQcWwIt4c41IUnvCQ2QWA 说明:使用了注解的方式进行对接口防刷的功能,非常高大上,本文章仅供 ...
- Springboot项目的接口防刷(实例)
技术要点:springboot的基本知识,redis基本操作, 首先是写一个注解类: import java.lang.annotation.Retention; import java.lang.a ...
- 页面接口防刷 解决思路一nginx
线上环境 很多接口 如果不做缓存 可能导致有人拿到url 每秒几万次的访问后台程序,导致系统down机. 此处, nginx可以加一层缓存. expires起到控制页面缓存的作用,合理的配置expi ...
- spring中实现基于注解实现动态的接口限流防刷
本文将介绍在spring项目中自定义注解,借助redis实现接口的限流 自定义注解类 import java.lang.annotation.ElementType; import java.lang ...
- 服务限流 -- 自定义注解基于RateLimiter实现接口限流
1. 令牌桶限流算法 令牌桶会以一个恒定的速率向固定容量大小桶中放入令牌,当有浏览来时取走一个或者多个令牌,当发生高并发情况下拿到令牌的执行业务逻辑,没有获取到令牌的就会丢弃获取服务降级处理,提示一个 ...
- 使用AOP+自定义注解完成spring boot的接口权限校验
记使用AOP+自定义注解完成接口的权限校验,代码如下: pom文件添加所需依赖: 1 <dependency> 2 <groupId>org.aspectj</group ...
- spring boot集成swagger,自定义注解,拦截器,xss过滤,异步调用,guava限流,定时任务案例, 发邮件
本文介绍spring boot集成swagger,自定义注解,拦截器,xss过滤,异步调用,定时任务案例 集成swagger--对于做前后端分离的项目,后端只需要提供接口访问,swagger提供了接口 ...
随机推荐
- 9.2 k8s结合Jenkins与gitlab实现代码升级与回滚
1.部署Jenkins 1.1 安装jdk # apt 安装jdk11 apt install openjdk-11-jdk # 查看 root@jenkins:~# java -version op ...
- Java 中的监控与管理原理概述
点赞再看,动力无限.Hello world : ) 微信搜「程序猿阿朗 」. 本文 Github.com/niumoo/JavaNotes 和 程序猿阿朗博客 已经收录,有很多知识点和系列文章. 当前 ...
- vue3 高阶 API 大汇总,强到离谱
高阶函数是什么呢? 高阶函数英文名叫:Higher Order function ,一个函数可以接收一个或多个函数作为输入,或者输出一个函数,至少满足上述条件之一的函数,叫做高阶函数. 前言 本篇内容 ...
- 生成&添加 SSH公钥
生成&添加 SSH公钥 生成 打开 Terminal(终端) 生成命令 ssh-keygen -t ed25519 -C "your_email@example.com" ...
- 系统发育树邻接法(NJ)和非加权组平均法(UPGMA)之比较
目录 1.原理的区别 2.实操比较 UPGMA NJ法 保存树文件 更深理解 1.原理的区别 主要区别在于,非加权组平均法(UPGMA)是基于平均链接方法的聚集层次聚类方法,而邻接法(NJ)是基于最小 ...
- R语言与医学统计图形-【17】ggplot2几何对象之热图
ggplot2绘图系统--heatmap.geom_rect 这里不介绍更常见的pheatmap包. 1.heatmap函数 基础包. data=as.matrix(mtcars) #接受矩阵 hea ...
- Linux运维工程师面试题整理
1. Nginx 反向代理,负载均衡,动静分离,工作原理及优化nginx配置反向代理. vim Nginx.confServer模块中配置Listen 80Server_name ip;在server ...
- Python基础之字典内置方法
目录 1. 字典 1.1 字典的作用 1.2 创建和使用字典 1.2.1 dict类 1.2.2 基本的字典操作 1.2.3 字典方法 1. 字典 映射:可以通过名称来访问其各个值的数据结构. 字典是 ...
- php背景透明png
php背景透明png php处理图片时,例如生成水印,对于png的水印经常背景会加有色的背景,用此方法可以去除背景 主要函数:imagecolortransparent: //添加水印 $src = ...
- day06 HTTP协议
day06 HTTP协议 HTTP协议 什么是http? HTTP 全称:Hyper Text Transfer Protocol 中文名:超文本传输协议 是一种按照URL指示,将超文本文档从一台主机 ...