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

相关推荐
爱吃烤鸡翅的酸菜鱼几秒前
Java算法OJ(8)随机选择算法
java·数据结构·算法·排序算法
码蜂窝编程官方3 分钟前
【含开题报告+文档+PPT+源码】基于SpringBoot+Vue的虎鲸旅游攻略网的设计与实现
java·vue.js·spring boot·后端·spring·旅游
Viktor_Ye20 分钟前
高效集成易快报与金蝶应付单的方案
java·前端·数据库
hummhumm22 分钟前
第 25 章 - Golang 项目结构
java·开发语言·前端·后端·python·elasticsearch·golang
一二小选手26 分钟前
【Maven】IDEA创建Maven项目 Maven配置
java·maven
J老熊32 分钟前
JavaFX:简介、使用场景、常见问题及对比其他框架分析
java·开发语言·后端·面试·系统架构·软件工程
猿java37 分钟前
什么是 Hystrix?它的工作原理是什么?
java·微服务·面试
AuroraI'ncoding38 分钟前
时间请求参数、响应
java·后端·spring
所待.3831 小时前
JavaEE之线程初阶(上)
java·java-ee
Winston Wood1 小时前
Java线程池详解
java·线程池·多线程·性能