只做一页的网站多少钱,网站开发就业趋势,深圳网页设计公司,请问婚庆网站建设该怎么做呢一#xff0c;概述
日常开发中会有一个常见的需求#xff0c;需要限制接口在单位时间内的访问次数#xff0c;比如说某个免费的接口限制单个IP一分钟内只能访问5次。该怎么实现呢#xff0c;通常大家都会想到用redis#xff0c;确实通过redis可以实现这个功能#xff0c…
一概述
日常开发中会有一个常见的需求需要限制接口在单位时间内的访问次数比如说某个免费的接口限制单个IP一分钟内只能访问5次。该怎么实现呢通常大家都会想到用redis确实通过redis可以实现这个功能下面实现一下。 二常见错误
固定时间窗口
有人设计了一个在每分钟内只允许访问1000次的限流方案如下图01:00s-02:00s之间只允许访问1000次。这种设计的问题在于请求可能在01:59s-02:00s之间被请求1000次02:00s-02:01s之间被请求了1000次这种情况下01:59s-02:01s间隔0.02s之间被请求2000次很显然这种设计是错误的。 三 实现
1基于滑动时间窗口 在指定的时间窗口内次数是累积的超过阈值都会限制。 2流程如下 3代码实现
前提pom文件引入redisSpring AOP等
1添加注解RequestLimit
package com.xxx.demo.aspect;import java.lang.annotation.*;/*** 接口访问频率注解默认一分钟只能访问10次*/
Documented
Target(ElementType.METHOD)
Retention(RetentionPolicy.RUNTIME)
public interface RequestLimit {// 限制时间 单位秒(默认值一分钟long period() default 60;// 允许请求的次数(默认值10次long count() default 10;
}
2添加切面实现注解的限制访问逻辑
package com.xxx.demo.aspect;import com.xgd.demo.commons.ErrorCode;
import com.xgd.demo.handler.BusinessException;
import com.xgd.demo.util.IpUtil;
import jakarta.servlet.http.HttpServletRequest;
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.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 org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.concurrent.TimeUnit;/*** date 2024/11/8 上午8:43*/
Aspect
Component
Log4j2
public class RequestLimitAspect {AutowiredRedisTemplate 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();// 请求ServletRequestAttributes attributes (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();assert attributes ! null;HttpServletRequest request attributes.getRequest();String ip IpUtil.getIpFromRequest(request);String uri request.getRequestURI();//设置客户端访问的keyString key req_limit_.concat(uri).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为{}, uri, limitCount, period, ip);throw new BusinessException(ErrorCode.REQUEST_LIMITED.getCode(), ErrorCode.REQUEST_LIMITED.getMessage());}// 继续执行请求return joinPoint.proceed();}
}上面里面请求被拦截是抛出了一个自定义的业务异常大家可以根据自己的情况自己定义。 3同时附上上面中引用到自定义工具类
package com.xxx.demo.util;import jakarta.servlet.http.HttpServletRequest;
import java.util.Objects;/*** date 2024/11/8 上午9:06*/
public class IpUtil {private static final String X_FORWARDED_FOR_HEADER X-Forwarded-For;private static final String X_REAL_IP_HEADER X-Real-IP;/*** 从请求中获取IP** return IP当获取不到时返回null*/public static String getIpFromRequest(HttpServletRequest request ) {return getRealIp(request);}/*** 获取请求的真实IP优先级从高到低为br/* 1.从请求头X-Forwarded-For中获取ip并且只获取第一个ip从左到右 br/* 2.从请求头X-Real-IP中获取ip br/* 3.使用{link HttpServletRequest#getRemoteAddr()}方法获取ip** param request 请求对象必须不能为null* return ip*/private static String getRealIp(HttpServletRequest request) {Objects.requireNonNull(request, request must be not null);String ip request.getHeader(X_FORWARDED_FOR_HEADER);if (ip ! null !ip.isBlank()) {int delimiterIndex ip.indexOf(,);if (delimiterIndex ! -1) {// 如果存在多个ip则取第一个ipip ip.substring(0, delimiterIndex);}return ip;}ip request.getHeader(X_REAL_IP_HEADER);if (ip ! null !ip.isBlank()) {return ip;} else {return request.getRemoteAddr();}}
}4使用注解
这里限制为10秒内只允许访问3次超过就抛出异常 5访问测试
前3次访问接口正常访问 后面的访问返回自定义异常的结果 如果对你有帮助记得点赞关注哟