接口限流防刷的目的
限制同一个用户一段时间之内只能访问固定次数,对系统做一层保护
实现思路
利用缓存实现,当用户每次点击访问接口的时候,在缓存中生成一个计数器,第一次请求的时候将这个计数器计数为1后存入缓存,并给其设定有效期,比如一分钟,如果一分钟之内再访问,那么数值加一;一分钟之内访问次数超过限定数值,就直接返回“访问过于频繁”;等到下一个一分钟,数据又重新从0开始计算,因为给缓存设定了一个有效期,所以一分钟之后会自动失效
- 获取访问路径
- 拼接用户的id作为一个记录该用户访问次数的key
- 从缓存里面取得该key做判断(以指定时间之内只能访问5次为例),如果在缓存里面没有取到,代表是第一次访问,然后给缓存设置该key,并设置初始value值为1;如果从缓存里面取得值并且小于5,那么直接将该key对应的value值+1;如果从缓存里面取的值>=5,那么代表在限制时间内访问次数达到限制
使用自定义注解+拦截器实现接口限流防刷
新建一个注解AccessLimit(用于设置在固定时间内限制的访问次数):
package com.javaxl.miaosha_05.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)//运行期间有效
@Target(ElementType.METHOD)//注解类型为方法注解
public @interface AccessLimit {
int seconds(); //固定时间
int maxCount();//最大访问次数
boolean needLogin() default true;// 用户是否需要登录
}
新建一个拦截器AccessInterceptor,继承HandlerInterceptorAdapter拦截器基类,通过实现preHandle方法,可以拿到方法上的自定义注解,判断访问次数count(从缓存中存取),然后根据注解上设置的时间,动态设置缓存的过期时间:
package com.javaxl.miaosha_05.interceptor;
import com.alibaba.fastjson.JSON;
import com.javaxl.miaosha_05.annotation.AccessLimit;
import com.javaxl.miaosha_05.domain.MiaoshaUser;
import com.javaxl.miaosha_05.redis.AccessKey;
import com.javaxl.miaosha_05.redis.RedisService;
import com.javaxl.miaosha_05.result.CodeMsg;
import com.javaxl.miaosha_05.result.Result;
import com.javaxl.miaosha_05.service.MiaoshaUserService;
import com.javaxl.miaosha_05.util.UserContext;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
@Service
public class AccessInterceptor extends HandlerInterceptorAdapter {
@Autowired
MiaoshaUserService miaoshaUserService;
@Autowired
RedisService redisService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (handler instanceof HandlerMethod) {
//先去取得用户做判断
MiaoshaUser user = getUser(request, response);
HandlerMethod hm = (HandlerMethod) handler;
AccessLimit aclimit = hm.getMethodAnnotation(AccessLimit.class);
//无该注解的时候,那么就不进行拦截操作
if (aclimit == null) {
return true;
}
//获取参数
int seconds = aclimit.seconds();
int maxCount = aclimit.maxCount();
boolean needLogin = aclimit.needLogin();
String key = request.getRequestURI();
if (needLogin) {//需要登录
if (user == null) {
//需要给客户端一个提示
render(response, CodeMsg.SESSION_ERROR);
return false;
}
key += "_" + user.getId();
}
//限定key5s之内只能访问5次,动态设置有效期
AccessKey akey = AccessKey.expire(seconds);
Integer count = redisService.get(akey, key, Integer.class);
if (count == null) {
redisService.set(akey, key, 1);
}
else if (count < maxCount) {
redisService.incr(akey, key);
}
else {//超过5次
//返回结果给前端
render(response, CodeMsg.ACCESS_LIMIT_REACHED);
return false;
}
}
return super.preHandle(request, response, handler);
}
private void render(HttpServletResponse response, CodeMsg cm) throws IOException {
//指定输出的编码格式,避免乱码
response.setContentType("application/json;charset=UTF-8");
OutputStream out = response.getOutputStream();
String jres = JSON.toJSONString(Result.error(cm));
out.write(jres.getBytes("UTF-8"));
out.flush();
out.close();
}
private MiaoshaUser getUser(HttpServletRequest request, HttpServletResponse response) {
String paramToken = request.getParameter(MiaoshaUserService.COOKIE_NAME_TOKEN);
String cookieToken = UserContext.getCookieValue(request, MiaoshaUserService.COOKIE_NAME_TOKEN);
if (StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) {
return null;
}
String token = StringUtils.isEmpty(paramToken) ? cookieToken : paramToken;
MiaoshaUser user = miaoshaUserService.getByToken(response, token);
return user;
}
}
在配置类WebConfig里将拦截器进行注册,这个类需继承WebMvcConfigurerAdapter(Spring框架的配置类):
package com.javaxl.miaosha_05.config;
import com.javaxl.miaosha_05.interceptor.AccessInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import java.util.List;
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Autowired
UserArgumentResolver userArgumentResolver;
@Autowired
AccessInterceptor accessInterceptor;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
//将UserArgumentResolver注册到config里面去
argumentResolvers.add(userArgumentResolver);
}
/**
* 注册拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册
registry.addInterceptor(accessInterceptor);
super.addInterceptors(registry);
}
}
使用如下: