接口限流防刷:
限制同一个用户一秒钟或者一分钟之内只能访问固定次数,在服务端对系统做一层保护。
思路:利用缓存实现,用户每次点击之后访问接口的时候,在缓存中生成一个计数器,第一次将这个计数器置1后存入缓存,并给其设定有效期,比如一分钟,一分钟之内再访问,那么数值加一。一分钟之内访问次数超过限定数值,直接返回失败。下一个一分钟,数据重新从0开始计算。因为缓存具有一个有效期,一分钟之后自动失效。
getMiaoshaPath代码:
@RequestMapping(value ="/getPath")
@ResponseBody
public Result getMiaoshaPath(HttpServletRequest request,Model model,MiaoshaUser user,
@RequestParam("goodsId") Long goodsId,
@RequestParam(value="vertifyCode",defaultValue="0") int vertifyCode) {
model.addAttribute("user", user);
//如果用户为空,则返回至登录页面
if(user==null){
return Result.error(CodeMsg.SESSION_ERROR);
}
//限制访问次数
String uri=request.getRequestURI();
String key=uri+"_"+user.getId();
//限定key5s之内只能访问5次
Integer count=redisService.get(AccessKey.access, key, Integer.class);
if(count==null) {
redisService.set(AccessKey.access, key, 1);
}else if(count<5) {
redisService.incr(AccessKey.access, key);
}else {//超过5次
return Result.error(CodeMsg.ACCESS_LIMIT);
}
//验证验证码
boolean check=miaoshaService.checkVCode(user, goodsId,vertifyCode );
if(!check) {
return Result.error(CodeMsg.REQUEST_ILLEAGAL);
}
System.out.println("通过!");
//生成一个随机串
String path=miaoshaService.createMiaoshaPath(user,goodsId);
return Result.success(path);
}
新建一个AccessKey作为访问限制的Key,设置一个固定有效期和一个动态设置有效期的Key前缀对象。
public class AccessKey extends BasePrefix{
//考虑页面缓存有效期比较短
public AccessKey(int expireSeconds,String prefix) {
super(expireSeconds,prefix);
}
//限制5s之内访问5次
public static AccessKey access=new AccessKey(5,"access");
//动态设置有效期
public static AccessKey expire(int expireSeconds) {
return new AccessKey(expireSeconds,"access");
}
}
优化:如何做一个通用的限流防刷逻辑?
思路:
每个方法都需要该判断功能,那么把它抽出来,定义一个拦截器,利用拦截器来拦截这些请求,判断次数,进行操作。
新建一个注解
@AccessLimit(seconds = 5,maxCount = 5,needLogin = true)
1、 新建注解,用于限流作用(在固定时间内限制访问次数)
@Retention(RetentionPolicy.RUNTIME)//运行期间有效
@Target(ElementType.METHOD)//注解类型为方法注解
public @interface AccessLimit {
int seconds(); //固定时间
int maxCount();//最大访问次数
boolean needLogin() default true;// 用户是否需要登录
}
2、实现拦截器,自定义AccessInterceptor继承HandlerInterceptorAdapter拦截器基类,通过实现这个接口,拿到方法上的注解
@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);
System.out.println("@AccessInterceptor---user"+user);
//将user保存下来
UserContext.setUser(user);
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();
System.out.println("------------:"+key);
if(needLogin) {
if(user==null) {
//需要给客户端一个提示
render(response,CodeMsg.SESSION_ERROR);
return false;
}
//需要的登录
key+="_"+user.getId();
}else {//不需要登录
//不需要操作
}
//限制访问次数
String uri=request.getRequestURI();
//String key=uri+"_"+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"token"
//遍历request里面所有的cookie
Cookie[] cookies=request.getCookies();
if(cookies!=null) {
for(Cookie cookie :cookies) {
if(cookie.getName().equals(cookie1NameToken)) {
System.out.println("getCookieValue:"+cookie.getValue());
return cookie.getValue();
}
}
}
System.out.println("No getCookieValue!");
return null;
}
}
UserContext 封装用户信息:
public class UserContext {
private static ThreadLocal userHolder=new ThreadLocal();
public static void setUser(MiaoshaUser user) {
userHolder.set(user);
}
public static MiaoshaUser getUser() {
return userHolder.get();
}
}
3、 将拦截器 注册到WebConfig中,这个类继承WebMvcConfigurerAdapter ,Spring框架的配置类。
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter{
@Autowired
UserArgumentResolver userArgumentResolver;
@Autowired
AccessInterceptor accessInterceptor;
@Override
public void addArgumentResolvers(List argumentResolvers) {
//将UserArgumentResolver注册到config里面去
argumentResolvers.add(userArgumentResolver);
}
/**
* 注册拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册
registry.addInterceptor(accessInterceptor);
super.addInterceptors(registry);
}
}