第一步 创建 CurrentLimi注释
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentLimit {
/**
* 请求次数
* @return
*/
int number();
/**
* 时间限制
*
* @return
*/
long time();
}
第二步 创建拦截器的类 CurrentLimitInterceptor
@Slf4j
@Component
public class CurrentLimitInterceptor implements HandlerInterceptor {
private final static String SEPARATOR = "-";
private final static String COMMA = ",";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ApplicationException {
RedisTemplate<String, Integer> redisTemplate = SpringBeanFactory.getBean("redisTemplate");
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
//通过HandlerMethod获取方法CurrentLimit注解
CurrentLimit currentLimit = handlerMethod.getMethodAnnotation(CurrentLimit.class);
//如果此方法存在限流注解
if (currentLimit != null) {
int number = currentLimit.number();
long time = currentLimit.time();
//如果次数和时间限制都大于0证明此处需要限流
if (time > 0 && number > 0) {
//这里的可以定义的是项目路径+API路径+ip。key可以根据项目实际场景去设定
String key = request.getContextPath() + SEPARATOR + request.getServletPath() + SEPARATOR + getIPAddress(request);
//获取reids缓存中的访问次数
Integer numberRedis = redisTemplate.opsForValue().get(key);
//如果是第一次访问,则设置此ip访问此API次数为1,并设置失效时间为注解中的时间
if (null == numberRedis) {
redisTemplate.opsForValue().set(key, 1, time, TimeUnit.SECONDS);
return true;
}
//如果访问次数大于注解设定则抛出异常
if (numberRedis >= number) {
throw new ApplicationException(I18nDbUtils.getI18nValue("request.frequently"));
}
//如果满足限流条件则更新缓存次数
redisTemplate.opsForValue().set(key, numberRedis + 1);
}
}
}
return true;
}
/**
* 获取当前网络 ip
* @param request HttpServletRequest
* @return 真实的 ip 地址
*/
private String getIPAddress(HttpServletRequest request) {
String ipAddress = request.getHeader("HTTP_X_FORWARDED_FOR");
if (isIpAddressBoolean(ipAddress)) {
ipAddress = request.getHeader("x-forwarded-for");
}
if (isIpAddressBoolean(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (isIpAddressBoolean(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (isIpAddressBoolean(ipAddress)) {
ipAddress = request.getRemoteAddr();
}
// 对于通过多个代理的情况,第一个 IP 为客户端真实 IP,多个 IP 按照','分割
// "***.***.***.***".length() = 15
if (ipAddress != null && ipAddress.length() > 15) {
if (ipAddress.indexOf(COMMA) > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
log.info("当前请求的ip地址是:" + ipAddress);
return ipAddress;
}
private boolean isIpAddressBoolean(String ipAddress) {
return ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress);
}
}
第三步 创建拦截器的配置文件 InterceptorConfig
@Slf4j
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Autowired
private CurrentLimitInterceptor currentLimitInterceptor;
/**
* 将自定义的限流拦截器添加到配置中
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//此处也可以通过addPathPatterns方法添加此拦截器对部分请求路径有效,也可以通过excludePathPatterns过滤请求路径
registry.addInterceptor(currentLimitInterceptor);
}
}
最后一步就是把注解添加到需要的接口上面,如下图所示:
java 防抖注解处理
于 2022-03-18 17:50:49 首次发布