【商城秒杀项目】-- 接口限流防刷

接口限流防刷的目的

限制同一个用户一段时间之内只能访问固定次数,对系统做一层保护

实现思路

利用缓存实现,当用户每次点击访问接口的时候,在缓存中生成一个计数器,第一次请求的时候将这个计数器计数为1后存入缓存,并给其设定有效期,比如一分钟,如果一分钟之内再访问,那么数值加一;一分钟之内访问次数超过限定数值,就直接返回“访问过于频繁”;等到下一个一分钟,数据又重新从0开始计算,因为给缓存设定了一个有效期,所以一分钟之后会自动失效

  1. 获取访问路径
  2. 拼接用户的id作为一个记录该用户访问次数的key
  3. 从缓存里面取得该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);
	}	
}

使用如下:

发布了133 篇原创文章 · 获赞 94 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/weixin_42687829/article/details/104516751