java 拦截器实现接口调用频率限制
在一些场景下,需要防止接口被攻击或者因频繁被调用导致系统卡顿,此时限制接口的调用频率可以起到一定程度的缓和效果。实现逻辑定义注解,配置频率,放在需要限制调用频率的接口上。定义拦截器拦截注解,如果拦截到定义的注解,则设置redis值,key为ip+接口名称,value为调用次数,保存redis且设置保存时间为指定时间,如果值大于指定的值,拦截器不放行返回拦截信息,如果没有超过值则放行,redis的k
·
在一些场景下,需要防止接口被攻击或者因频繁被调用导致系统卡顿,此时限制接口的调用频率可以起到一定程度的缓和效果。
实现逻辑
定义注解,配置频率,放在需要限制调用频率的接口上。定义拦截器拦截注解,如果拦截到定义的注解,则设置redis值,key为ip+接口名称,value为调用次数,保存redis且设置保存时间为指定时间,如果值大于指定的值,拦截器不放行返回拦截信息,如果没有超过值则放行,redis的key不变,值加1。
1、定义注解
其中count为访问次数,time为指定时间
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
@Inherited
@Documented
@Order(Ordered.HIGHEST_PRECEDENCE) //最高优先级
public @interface RequestLimit {
/**
*
* 允许访问的次数,默认值MAX_VALUE
*/
int count() default Integer.MAX_VALUE;
/**
*
* 时间段,单位为毫秒,默认值一分钟
*/
long time() default 60000;
}
2、使用注解
如下在控制器上添加@RequestLimit(count = 2),标识默认时间内可以调用两次接口,默认时间为1分钟,即接口1分钟仅可以被调用两次
@RequestMapping(value = {"getDocRecvCount"}
@RequestLimit(count = 2)
public OpenRespModel getCount(@RequestBody(required = false) OpenModel param) {
return this.openService.getCount(param);
}
3、拦截器配置
拦截到指定的注解,通过设置redis的数据,key为ip加上接口,value为已经调用的次数,redis数据有效时间为定义的时间
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import com..commons.annotation.RequestLimit;
import com..commons.model.ResponseCodeEnum;
import com..util.RequestUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class RequestLimitInterceptor extends HandlerInterceptorAdapter {
private static final Logger logger = LoggerFactory.getLogger("RequestLimitInterceptor");
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//方法注解
RequestLimit methodAnnotation = ((HandlerMethod) handler).getMethodAnnotation(RequestLimit.class);
//类注解
RequestLimit classAnnotation = ((HandlerMethod) handler).getBean().getClass().getAnnotation(RequestLimit.class);
boolean vcode = true;
if (methodAnnotation != null) {
vcode = validateCode(request, methodAnnotation.count(), methodAnnotation.time());
} else if (classAnnotation != null) {
vcode = validateCode(request, classAnnotation.count(), classAnnotation.time());
}
if (vcode) {
return true;
} else {
Map<String, Object> resultMap = Maps.newHashMap();
resultMap.put("retCode", ResponseCodeEnum.REQUESTFULL.getRetCode());
resultMap.put("retDesc", ResponseCodeEnum.REQUESTFULL.getRetDesc());
try {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json;charset=UTF-8");
PrintWriter pw = response.getWriter();
pw.write(JSON.toJSONString(resultMap));
pw.flush();
pw.close();
} catch (IOException e) {
logger.error("返回页面数据出错!" + e.getMessage(), e);
throw e;
}
return false;
}
}
/**
* 接口的访问频次限制
*
* @param request
* @return
*/
private boolean validateCode(HttpServletRequest request, int maxSize, long timeOut) {
boolean resultCode = true;
try {
String ip = RequestUtil.getRemoteAddr(request);
String url = request.getRequestURL().toString();
String key = "req_limit_".concat(url).concat(ip);
long count = redisTemplate.opsForValue().increment(key, 1);
if (count == 1) {
redisTemplate.expire(key, timeOut, TimeUnit.MILLISECONDS);
}
if (count > maxSize) {
logger.info("用户IP[" + ip + "]访问地址[" + url + "]超过了限定的次数[" + maxSize + "]");
resultCode = false;
}
} catch (Exception e) {
logger.error("发生异常: ", e);
}
return resultCode;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
4、拦截器添加到项目中
<!-- 拦截提供外部接口访问次数限制-->
<mvc:interceptor>
<mvc:mapping path="/openApi/**" />
<bean class="com..commons.interceptor.RequestLimitInterceptor" />
</mvc:interceptor>
第一,二次调用结果
第三次调用结果
过一分钟后等redis设置值失效后,便可以再次访问。
学海无涯苦作舟!!!
更多推荐



所有评论(0)