当前位置: 首页 > news >正文

SpringAOP+自定义注解实现限制接口访问频率,利用滑动窗口思想Redis的ZSet(附带整个Demo)

目录

1.创建切面

2.创建自定义注解

3.自定义异常类

4.全局异常捕获

5.Controller层

 


demo的地址,自行获取《《——————————————————————————

Spring Boot整合Aop面向切面编程实现权限校验,SpringAop+自定义注解+自定义异常+全局异常捕获,实现权限验证,要求对每个接口都实现单独的权限校验

Spring Boot整合Aop面向切面编程实现权限校验,SpringAop+自定义注解+自定义异常+全局异常捕获,实现权限验证,要求对每个接口都实现单独的权限校验

背景

在日常开发中,为了保证系统稳定性,防止被恶意攻击,我们可以控制用户访问接口的频率,

 颜色部分表示窗口大小

在指定时间内,只能允许访问N次,我们将这个指定时间T,看出一个滑动的窗口宽度,Redis的zset的score为滑动窗口,在操作zset的时候,只保留窗口数据,删除其他数据

1.创建切面

package com.example.aspect;import com.example.annotation.RequestLimiting;
import com.example.exception.ConditionException;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.operators.arithmetic.Concat;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;@Aspect
@Slf4j
@Component
public class ApiLimitedRoleAspect {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Pointcut("@annotation(com.example.annotation.RequestLimiting)")public void poincut() {}@Around("poincut() && @annotation(requestLimiting)")public Object doAround(ProceedingJoinPoint proceedingJoinPoint, RequestLimiting requestLimiting) throws Throwable {//获取注解参数long period = requestLimiting.period();long count = requestLimiting.count();ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = requestAttributes.getRequest();//获取ip和urlString ip = request.getRemoteAddr();String uri = request.getRequestURI();//拼接redis的key值String key = "reqLimiting" + uri + ip;ZSetOperations<String, String> zSetOperations = stringRedisTemplate.opsForZSet();//获取当前时间long curTime = System.currentTimeMillis();//添加当前时间zSetOperations.add(key, String.valueOf(curTime), curTime);//设置过期时间stringRedisTemplate.expire(key, period, TimeUnit.SECONDS);//删除窗口之外的值//这个a指的是实际窗口的范围边界,比如,我从10:00开始访问,//每秒访问1次,在11:12时,记录里就会有62条,此时的a表示10:12,//最后会删除掉10:00-10:12的所有key,留下的都10:12-11:12的keyLong a = curTime - period * 1000;log.error(String.valueOf(a));zSetOperations.removeRangeByScore(key, 0, a);//设置访问次数Long acount = zSetOperations.zCard(key);if (acount > count) {log.error("接口拦截:{}请求超过限制频率{}次/{}s,ip为{}", uri, count, period, ip);throw new ConditionException("10004", "接口访问受限,请稍后");}return proceedingJoinPoint.proceed();}}

2.创建自定义注解

package com.example.annotation;import java.lang.annotation.*;/**
*<p> </p>
*自定义注解* 接口访问频率
*@author panwu
*@descripion
*@date 2024/3/24 13:23
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestLimiting {//窗口宽度long period() default  60;//允许访问次数long count() default  5;
}

 3.自定义异常类

package com.example.exception;public class ConditionException extends RuntimeException {private static final long serialVersionUID = 1L;private String code;//    public ConditionException(ConditionExceptionEnum conditionExceptionEnum){
//        super(conditionExceptionEnum.getDesc());
//        this.code = conditionExceptionEnum.getCode();
//    }public ConditionException(String code, String name) {super(name);this.code = code;}public ConditionException(String name){super(name);code="500";}public String getCode() {return code;}public void setCode(String code) {this.code = code;}}

4.全局异常捕获

package com.example.exception;import com.example.dto.Result;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;@ControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ComonGlobalExceptionHandler {@ExceptionHandler(value = ConditionException.class)@ResponseBodypublic Result conditionException(ConditionException e){return Result.fail(e.getCode(),e.getMessage());}
}

5.Controller层

package com.example.controller;import com.example.annotation.RequestLimiting;
import com.example.dto.Result;
import lombok.extern.log4j.Log4j;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {@GetMapping()@RequestLimiting(count = 3)public Result get(){log.debug("访问成功");return Result.ok("访问成功");}
}

要知道的是,该Demo实现的是对同一用户的反复点击进行频率限制,也可用作幂等性校验,具体值需要设置,还需要考虑分布式情况,加上分布式锁

相关文章:

  • Github 2024-03-28 开源项目日报 Top10
  • scala-idea环境搭建及使用
  • 扩展wordpress回收站功能
  • SpringBoot SpringMVC (详解)
  • 《数据结构学习笔记---第三篇》---单链表具体实现
  • 提升JavaScript代码质量的最佳实践
  • 2024最新华为OD机试试题库全 -【幼儿园圆桶的取出顺序】- C卷
  • LNMP架构之mysql数据库实战
  • easyExcel大数据量导出oom
  • [BT]BUUCTF刷题第9天(3.27)
  • 整数的反转
  • 离线数仓(八)【DWD 层开发】
  • 芯片工程系列(5)2.5D 3D封装
  • 13 Games101 - 笔记 - 光线追踪(Whitted-Style光线追踪原理详解及实现细节)
  • docker日志大小设置(doker logs)
  • 【编码】-360实习笔试编程题(二)-2016.03.29
  • 【许晓笛】 EOS 智能合约案例解析(3)
  • 2017-08-04 前端日报
  • Debian下无root权限使用Python访问Oracle
  • echarts的各种常用效果展示
  • java 多线程基础, 我觉得还是有必要看看的
  • javascript数组去重/查找/插入/删除
  • leetcode98. Validate Binary Search Tree
  • mongodb--安装和初步使用教程
  • Python 使用 Tornado 框架实现 WebHook 自动部署 Git 项目
  • VUE es6技巧写法(持续更新中~~~)
  • vue自定义指令实现v-tap插件
  • windows下如何用phpstorm同步测试服务器
  • 利用DataURL技术在网页上显示图片
  • 浏览器缓存机制分析
  • 那些年我们用过的显示性能指标
  • 深入浏览器事件循环的本质
  • 我的zsh配置, 2019最新方案
  • 写代码的正确姿势
  • 看到一个关于网页设计的文章分享过来!大家看看!
  • AI又要和人类“对打”,Deepmind宣布《星战Ⅱ》即将开始 ...
  • RDS-Mysql 物理备份恢复到本地数据库上
  • #多叉树深度遍历_结合深度学习的视频编码方法--帧内预测
  • (20)目标检测算法之YOLOv5计算预选框、详解anchor计算
  • (html5)在移动端input输入搜索项后 输入法下面为什么不想百度那样出现前往? 而我的出现的是换行...
  • (JS基础)String 类型
  • (Redis使用系列) Springboot 实现Redis 同数据源动态切换db 八
  • (SpringBoot)第七章:SpringBoot日志文件
  • (蓝桥杯每日一题)平方末尾及补充(常用的字符串函数功能)
  • (论文阅读26/100)Weakly-supervised learning with convolutional neural networks
  • (学习日记)2024.01.19
  • (转)http-server应用
  • (转)http协议
  • (转)socket Aio demo
  • *** 2003
  • ***检测工具之RKHunter AIDE
  • .NET Core中Emit的使用
  • .net 简单实现MD5
  • .NET 简介:跨平台、开源、高性能的开发平台
  • .Net8 Blazor 尝鲜