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.创建切面

java 复制代码
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 {
    @Autowired
    private 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和url
        String 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的key
        Long 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.创建自定义注解

java 复制代码
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.自定义异常类

java 复制代码
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.全局异常捕获

java 复制代码
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)
    @ResponseBody
    public Result conditionException(ConditionException e){
        return Result.fail(e.getCode(),e.getMessage());
    }
}

5.Controller层

java 复制代码
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实现的是对同一用户的反复点击进行频率限制,也可用作幂等性校验,具体值需要设置,还需要考虑分布式情况,加上分布式锁

相关推荐
Q_19284999066 分钟前
基于Spring Boot的九州美食城商户一体化系统
java·spring boot·后端
Q_192849990610 分钟前
基于Spring Boot的营销项目系统
spring boot
张国荣家的弟弟24 分钟前
【Yonghong 企业日常问题 06】上传的文件不在白名单,修改allow.jar.digest属性添加允许上传的文件SH256值?
java·jar·bi
ZSYP-S35 分钟前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
yuanbenshidiaos42 分钟前
C++----------函数的调用机制
java·c++·算法
是小崔啊1 小时前
开源轮子 - EasyExcel01(核心api)
java·开发语言·开源·excel·阿里巴巴
黄公子学安全1 小时前
Java的基础概念(一)
java·开发语言·python
liwulin05061 小时前
【JAVA】Tesseract-OCR截图屏幕指定区域识别0.4.2
java·开发语言·ocr
jackiendsc1 小时前
Java的垃圾回收机制介绍、工作原理、算法及分析调优
java·开发语言·算法
Yuan_o_1 小时前
Linux 基本使用和程序部署
java·linux·运维·服务器·数据库·后端