要知道,如今很多平台的接口都是可以同时被门户网站,手机端,移动浏览器访问,因为接口是通用的,而为了安全起见,有些接口都会设置一个门槛,那就是限制访问次数,也就是在某一时间段内不能过多的访问,比如登录次数限制,在一些金融理财或者银行的接口上比较常见,另外一些与用户信息有关的接口都会有一个限制门槛
那么这个限制门槛怎么来做呢,其实有很多种方法,主流的做法可以用拦截器或者注解,那么今天咱们用注解来实现
首先需要定义一个注解,如下:
/**
*
* @Title: LimitIPRequest.java
* @Package com.agood.bejavagod.component
* @Description: 限制某个IP在某个时间段内请求某个方法的次数
* Copyright: Copyright (c) 2016
* Company:Nathan.Lee.Salvatore
*
* @author leechenxiang
* @date 2016年12月14日 下午8:16:49
* @version V1.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
@Order(Ordered.HIGHEST_PRECEDENCE) // 设置顺序为最高优先级
public @interface LimitIPRequest {
/**
*
* @Description: 限制某时间段内可以访问的次数,默认设置100
* @return
*
* @author leechenxiang
* @date 2016年12月14日 下午8:22:29
*/
int limitCounts() default 100;
/**
*
* @Description: 限制访问的某一个时间段,单位为秒,默认值1分钟即可
* @return
*
* @author leechenxiang
* @date 2016年12月14日 下午8:21:59
*/
int timeSecond() default 60;
}
然后再使用spring aop,拦截被你注解的那个controller的方法
@Aspect
@Component
public class LimitIPRequestDisplay {
@Autowired
private JedisClient jedis;
@Pointcut("execution(* com.agood.bejavagod.controller.*.*(..)) && @annotation(com.agood.bejavagod.component.LimitIPRequest)")
public void before(){
}
@Before("before()")
public void requestLimit(JoinPoint joinPoint) throws LimitIPRequestException {
try {
// 获取HttpRequest
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
HttpServletResponse response = attributes.getResponse();
// 判断request不能为空
if (request == null) {
throw new LimitIPRequestException("HttpServletRequest有误...");
}
LimitIPRequest limit = this.getAnnotation(joinPoint);
if(limit == null) {
return;
}
String ip = request.getRemoteAddr();
String uri = request.getRequestURI().toString();
String redisKey = "limit-ip-request:" + uri + ":" + ip;
// 设置在redis中的缓存,累加1
long count = jedis.incr(redisKey);
// 如果该key不存在,则从0开始计算,并且当count为1的时候,设置过期时间
if (count == 1) {
jedis.expire(redisKey, limit.timeSecond());
// redisTemplate.expire(redisKey, limit.time(), TimeUnit.MILLISECONDS);
}
// 如果redis中的count大于限制的次数,则报错
if (count > limit.limitCounts()) {
// logger.info("用户IP[" + ip + "]访问地址[" + url + "]超过了限定的次数[" + limit.count() + "]");
if (ShiroFilterUtils.isAjax(request)) {
HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
httpServletResponse.sendError(ShiroFilterUtils.HTTP_STATUS_LIMIT_IP_REQUEST);
} else {
throw new LimitIPRequestException();
}
}
} catch (LimitIPRequestException e) {
throw e;
} catch (Exception e) {
e.printStackTrace();
}
}
/**
*
* @Description: 获得注解
* @param joinPoint
* @return
* @throws Exception
*
* @author leechenxiang
* @date 2016年12月14日 下午9:55:32
*/
private LimitIPRequest getAnnotation(JoinPoint joinPoint) throws Exception {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null) {
return method.getAnnotation(LimitIPRequest.class);
}
return null;
}
}
这个类使用了redis缓存作为计数器,因为好用,当然你用静态的map也行,但是考虑的分布式集群的话一般还是建议使用redis比较好。
大致的流程就是要获取redis中的调用方法次数,使用incr函数,当key不存在的时候默认为0然后累加1,当累加1大于limit设置的限制次数时,则抛出异常,这个地方需要注意,如果是ajax调用的话需要判断是否ajax,然后再返回错误信息
image
查看redis中key的剩余时间:
image
好,那么按照如上方法就能实现对接口访问次数的限制
微信公众号:BeJavaGodJava技术交流群
网友评论