SpringBoot限制(限流)接口访问频率

限流整个流程过程

1.首先用户的请求进来,将用户ip和uri组成key,timestamp为value,放入zset
2. 更新当前key的缓存过期时间,这一步主要是为了定期清理掉冷数据,和上面我提到的常见错误设计2中的意义不同
3. 删除窗口之外的数据记录
4. 统计当前窗口中的总记录数
5. 如果记录数大于阈值,则直接返回错误,否则正常处理用户请求

首先是定义一个注解,方便后续对不同接口使用不同的限制频率

package org.jeecg.common.aspect.annotation;

import java.lang.annotation.*;

/**
 * @Author xu
 * @create 2023/8/2 19
 */
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestLimit {

    // 限制时间 单位:秒(默认值:一分钟)
    long period() default 60;

    // 允许请求的次数(默认值:5次)
    long count() default 5;

}

切面AOP处理逻辑

package org.jeecg.common.aspect;

import lombok.extern.log4j.Log4j2;
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.jeecg.common.aspect.annotation.RequestLimit;
import org.jeecg.common.exception.JeecgBootException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;

/**
 * @Author xu
 * @create 2023/8/2 19
 */
@Aspect
@Component
@Log4j2
public class RequestLimitAspect {

    @Autowired
    RedisTemplate redisTemplate;

    // 切点
    @Pointcut("@annotation(requestLimit)")
    public void controllerAspect(RequestLimit requestLimit) {
    }

    @Around("controllerAspect(requestLimit)")
    public Object doAround(ProceedingJoinPoint joinPoint, RequestLimit requestLimit) throws Throwable {
        long period = requestLimit.period();
        long limitCount = requestLimit.count();
        Object[] args = joinPoint.getArgs();
        String ip = null;
        String url = null;
        for (Object arg : args) {
            if (arg instanceof HttpServletRequest) {
                HttpServletRequest request = (HttpServletRequest) arg;
                ip = request.getRemoteAddr();
                url = request.getRequestURI();
                break;  // 如果找到了符合条件的参数,可以选择跳出循环
            }
        }
        String key = "req_limit_".concat(url).concat(ip);
        ZSetOperations zSetOperations = redisTemplate.opsForZSet();
        long currentMs = System.currentTimeMillis();
        zSetOperations.add(key, currentMs, currentMs);
        redisTemplate.expire(key, period, TimeUnit.SECONDS);
        zSetOperations.removeRangeByScore(key, 0, currentMs - period * 1000);
        Long count = zSetOperations.zCard(key);
        if (count > limitCount) {
            log.error("接口拦截:{} 请求超过限制频率【{}次/{}s】,IP为{}", url, limitCount, period, ip);
            throw new JeecgBootException("请求太频繁,请稍后再试");
        }

        return joinPoint.proceed();
    }

}

Controller层使用

	@AutoLog(value = "访客数据-添加")
	@RequestLimit(count = 2,period = 20)
	@ApiOperation(value="访客数据-添加", notes="访客数据-添加")
	@PostMapping(value = "/verifySave")
	public Result<?> verifySave(@RequestBody SysVisitantData sysVisitantData,HttpServletRequest request) {
		String ip = request.getRemoteAddr();
		String url = request.getRequestURI();
		return Result.OK("添加成功!");
	}

你可能感兴趣的:(spring,boot,spring,java)