首页 > 技术文章 > 模块5之实现接口限流,用户登录控制

deijiawoyu 2020-04-10 10:36 原文

简介:实现自定义注解@AccessLimit,本质为一个拦截器

定义一个注解类,一个AccessInterceptor类继成HandlerInterceptorAdapter抽象类,并重写preHandle方法。实现用户自定义某时间段内某用户对某商品的访问次数。

第一步:创建一个注解类@Interface

 1 import java.lang.annotation.Retention;
 2 import java.lang.annotation.Target;
 3 
 4 import static java.lang.annotation.ElementType.METHOD;
 5 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 6 
 7 @Retention(RUNTIME)
 8 @Target(METHOD)
 9 public @interface AccessLimit {
10     int seconds();
11     int maxCount();
12     boolean needLogin() default true;
13 }

添加两个元注解和三个抽象方法。

在注解类上使用的注解被称为元注解,本注解类使用了两个元注解@Retention(RUNTIME)和@Target(METHOD)。

@Retention(RUNTIME):@Retention()注解指定@AccessLimit的生命周期,RUNTIME表示该注解在程序运行结束之前被保留。

@Target(METHOD):@Target指定可以使用@AccessLimit的元素,METHOD参数表示可以在方法上使用该注解。更多参数请查看  java.lang.annotation.ElementType。

第二步:创建AccessLimit.java继承抽象类HandlerInterceptorAdapter.java,重写preHandle方法。

SpringMVC处理web请求的基本流程为,请求经过DispatcherServlet的分发后,

按照一定的顺序执行一系列的Interceptor中的预处理方法,如果预处理方法返回true,则程序继续走向下一个预处理方法,或处理器方法(Controller中的方法);

返回false,请求处理流程中断,此时需要通过response产生响应。

  1 import com.alibaba.druid.util.StringUtils;
  2 import com.alibaba.fastjson.JSON;
  3 import com.app.miaosha.Pojo.MiaoshaUser;
  4 import com.app.miaosha.Redis.AccessPrefix;
  5 import com.app.miaosha.Redis.RedisService;
  6 import com.app.miaosha.Result.CodeMsg;
  7 import com.app.miaosha.Result.Result;
  8 import com.app.miaosha.Service.MiaoshaUserService;
  9 import org.springframework.beans.factory.annotation.Autowired;
 10 import org.springframework.stereotype.Service;
 11 import org.springframework.web.method.HandlerMethod;
 12 import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
 13 
 14 import javax.servlet.http.Cookie;
 15 import javax.servlet.http.HttpServletRequest;
 16 import javax.servlet.http.HttpServletResponse;
 17 import java.io.IOException;
 18 import java.io.OutputStream;
 19 
 20 @Service
 21 public class AccessIntercepter extends HandlerInterceptorAdapter {                                                      //1.实现HandlerInterceptorAdapter抽象类成为拦截器
 22     @Autowired
 23     MiaoshaUserService miaoshaUserService;
 24     @Autowired
 25     RedisService redisService;
 26 
 27 
 28 
 29     /*
 30     * 2.预处理方法:在请求到达处理器方法之前被调用
 31     * */
 32     @Override
 33     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
 34 
 35      
    //3.HandlerMethod:对应一个@Controller下的@RequestMapping的方法存放很多该处理器方法的信息handler:表示任意的前端传递来的请求,如对静态资源的请求
 36   if (handler instanceof HandlerMethod) {  

          MiaoshaUser user = getMiaoshaUser(request,response); 40 UserContext.setUser(user); 41 HandlerMethod hm = (HandlerMethod) handler; 42 AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);//4.得到处理器方法前是否使用了@AccessLimit 43 if (accessLimit == null) { 44 return true; 45 } 46 int seconds = accessLimit.seconds(); 47 int maxCount = accessLimit.maxCount(); 48 boolean login = accessLimit.needLogin(); 49 String key = request.getRequestURI(); 50 //需要是否需要进行登录 51 if (login) { 52 if (user == null) { 53 render(response, CodeMsg.SESSION_ERROR); 54 return false; 55 } 56 key+="_"+user.getId(); 57 }else { 58 } 59 //实现接口限流 60 Integer count = redisService.get(AccessPrefix.setExpirSeconds(seconds),""+key,Integer.class); 61 if (count==null) { //如果还没有被访问过 62 redisService.set(AccessPrefix.setExpirSeconds(seconds),""+key,1); 63 }else if (count<maxCount) { //如果访问次数没有超过规定的最大值 64 redisService.incr(AccessPrefix.setExpirSeconds(seconds),""+key); 65 }else { //如果访问超过了规定的最大值 66 render(response,CodeMsg.ACCESS_LIMIT_REACHED); 67 return false; 68 } 69 } 70 return true; 71 } 72 73 74 /* 75 * 将CodeMsg放入到response中 76 * */ 77 private void render(HttpServletResponse response, CodeMsg sessionError) throws IOException { 78 OutputStream outputStream = response.getOutputStream(); 79 response.setContentType("application/json;charset=UTF-8"); 80 String cm = JSON.toJSONString(Result.error(sessionError)); 81 outputStream.write(cm.getBytes("UTF-8")); 82 outputStream.flush(); 83 outputStream.close(); 84 } 85 /** 86 * 通过request提供的cookie获得MiaoShaUser对象 87 */ 88 private MiaoshaUser getMiaoshaUser(HttpServletRequest request, HttpServletResponse response){ 89 String paramToken = request.getParameter(MiaoshaUserService.TOKEN_NAME); 90 String cookieToken = getCookieValue(request,MiaoshaUserService.TOKEN_NAME); 91 //使用cookie得到对象 92 if (StringUtils.isEmpty(cookieToken)&&StringUtils.isEmpty(paramToken)) { 93 return null; 94 } 95 String token = StringUtils.isEmpty(paramToken)?cookieToken:paramToken; //优先取paraToken 96 MiaoshaUser user = miaoshaUserService.getByToken(token,response); 97 return user; 98 } 99 100 /* 101 * 获得request中的cookie值 102 */ 103 private String getCookieValue(HttpServletRequest request, String tokenName) { 104 Cookie[] cookies = request.getCookies(); 105 if(cookies==null || cookies.length<=0){ 106 return null; 107 } 108 for (Cookie c:cookies 109 ) { 110 if (c.getName().equals(tokenName)) { 111 return c.getValue(); 112 } 113 } 114 return null; 115 } 116 }

 上面代码实现功能的逻辑

创建一个注解类@Interface AccessLimit,用来获取用户在注解中填写的信息。

*

*

创建AccessIntercepter.java类并继承了HandlerIntercepetorAdapter.java,成为了一个拦截器。
*
*
继承了HandlerIntercepetorAdapter的类成为一个拦截器,前端发送的请求会先调用一系列的拦截器的预处理方法即preHandle方法
如果preHandle方法返回true,就继续往下执行,返回false,就使用response向前端返回异常
*
*
在preHandle中开始实现接口限流功能
*
*
1.判断前端发来的请求handler是否为一个匹配了@RequestMapping注解的方法可以处理的请求HandlerMethod。如果不是返回true,否则继续进行。
*
*
2.判断handler对应的方法是否使用了@AccessLimit注解,如果没有返回true,否则继续进行。
*
*
3.获取注解中被填写的参数值:AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);

accessLimit中提供了方法来获取参数值。
*
*
4.根据3中的参数判断是否需要登录,如果不需要登录,返回true;如果需要登录,判断登录情况,并将当前URI+user.id组合成key,等待将来使用。
*
*
5.使用redis来记录用户某段时间内对某个接口的访问次数,key为4中的key,value为maxCount,expiredTime为时间长度。

*

*

preHandle方法最后返回true。

 

推荐阅读