模块5之实现接口限流,用户登录控制
简介:实现自定义注解@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。
posted on 2020-04-10 10:36 hello,bdiskl 阅读(535) 评论(0) 编辑 收藏 举报