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 {
    @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.创建自定义注解

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

  • 27
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值