基于注解的接口限流+统一session认证

代码心得:

一个基本的做法:对于用户身份认证做到拦截器里,针对HandlerMethod进行统一拦截认证,根据方法上的注解标识,判别是否需要身份验证,并将查找出来的User实体存入ThreadLocal,在本次请求中,以后的目标方法都可以从ThreadLocal里取出user,并在afterCompletion方法里,对ThreadLocal做清理,也可以通过HandlerMethodArgumentResolver针对某一种参数类型(UserParameter.class)编写统一的用户认证,当然,也可以先拦截,然后在ArgumentResolver里通过ThreadLocal获取user注入到controller方法里的参数

tip:一个请求(即一个thread),通过拦截器将DB中的对象存入ThreadLocal,之后目标方法,或本次请求中的其他后续方法,都可以通过ThreadLocal获取该对象,然后在afterCompletion清理ThreadLocal,表示一个请求结束。当然,拦截器里可以在redis,如果session失效,可以重定向到登陆。

拦截器主要用来拦截handlerMethod(controller方法),aop用于service层面(也可以用于某些工具类,或者controller)的非业务统一处理,前置后置处理器用于全部bean的初始化前或初始化后的统一代码织入

基于注解的接口限流+统一session认证代码实现:

第一步:编写AccessLimit注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AccessLimit {//控制单位时间内的最大访问次数

    int maxCount();//注解特点:标记属性即调用方法

    int seconds();

    boolean needLogin() default true;//是否需要用户身份

}

第二步:编写拦截器

@Component
public class AccessInterceptor  extends HandlerInterceptorAdapter{
    
    @Autowired
    MiaoshaUserService userService;
    
    @Autowired
    RedisService redisService;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        if(handler instanceof HandlerMethod) {
            MiaoshaUser user = getUser(request, response);
            UserContext.setUser(user);// 请求(一个thread)->拦截器->AOP->目标方法->清理user
            HandlerMethod hm = (HandlerMethod)handler;//对controller里的方法做了封装,避免运行时使用反射
            AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
            if(accessLimit == null) {
                return true;
            }
            int seconds = accessLimit.seconds();
            int maxCount = accessLimit.maxCount();
            boolean needLogin = accessLimit.needLogin();
            String key = request.getRequestURI();
            if(needLogin) {
                if(user == null) {
                    render(response, CodeMsg.SESSION_ERROR);//可以重定向到登陆或向浏览器输出json
                    return false;//返回false拦截,返回true放行
                }
                key += "_" + user.getId();//  接口防刷:需要用户登陆的 接口路径+用户id做key  不要的:接口路径做key
            }else {
                //do nothing
            }
        //redis限速器 AccessKey ak
= AccessKey.withExpire(seconds); Integer count = redisService.get(ak, key, Integer.class); if(count == null) { redisService.set(ak, key, 1); }else if(count < maxCount) { redisService.incr(ak, key); }else { render(response, CodeMsg.ACCESS_LIMIT_REACHED);//访问 达到 限制 return false; } } return true; } //向浏览器写出json private void render(HttpServletResponse response, CodeMsg cm)throws Exception { response.setContentType("application/json;charset=UTF-8"); OutputStream out = response.getOutputStream(); String str = JSON.toJSONString(Result.error(cm)); out.write(str.getBytes("UTF-8"));// out.write(byte[]) out.flush(); out.close(); } //从请求中获取用户信息 private MiaoshaUser getUser(HttpServletRequest request, HttpServletResponse response) {
  //从request或cookie中获取token,防止不支持cookie的client String paramToken
= request.getParameter(MiaoshaUserService.COOKI_NAME_TOKEN); String cookieToken = getCookieValue(request, MiaoshaUserService.COOKI_NAME_TOKEN); if(StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) { return null; } String token = StringUtils.isEmpty(paramToken)?cookieToken:paramToken; return userService.getByToken(response, token);// service里也是返回null,最上级在根据null处理异常 } private String getCookieValue(HttpServletRequest request, String cookiName) { Cookie[] cookies = request.getCookies(); if(cookies == null || cookies.length <= 0){ return null; } for(Cookie cookie : cookies) { if(cookie.getName().equals(cookiName)) { return cookie.getValue(); } } return null; }

将编写好的拦截器加入webConfig(即继承webConfigAdapter)中,即可

使用:
@AccessLimit(seconds=5, maxCount=5, needLogin=true)
@RequestMapping(value="/path", method=RequestMethod.GET)
@ResponseBody
public Result<String> getMiaoshaPath(HttpServletRequest request, MiaoshaUser user){...}

 

posted @ 2018-10-11 11:25  brx_blog  阅读(1067)  评论(0编辑  收藏  举报