zoukankan      html  css  js  c++  java
  • 基于注解的接口限流+统一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){...}
  • 相关阅读:
    ClickHouse 详解
    SparkStreaming(二)--SparkStreaming整合Kafka
    SparkStreaming(一)--核心概念及算子
    毕设进度-3月22日
    毕设进度-3月21日
    毕设进度-3月14日
    毕设进度-3月13日
    毕设进度-3月12日
    毕设进度-3月11日
    毕设进度-3月10日
  • 原文地址:https://www.cnblogs.com/brxHqs/p/9771447.html
Copyright © 2011-2022 走看看