zoukankan      html  css  js  c++  java
  • 利用Filter和拦截器,将用户信息动态传入Request方法

    前言:

    • 在开发当中,经常会验证用户登录状态和获取用户信息。如果每次都手动调用用户信息查询接口,会非常的繁琐,而且代码冗余。为了提高开发效率,因此就有了今天这篇文章。

    思路:

    • 用户请求我们的方法会携带一个Token,通过Filter过滤器将会员信息查出来并放到request请求参数中。接着在Cotroller层的请求方法中接收一个MemberDeatails类型的参数,就能直接获得会员信息了。

    详细步骤:

    1. Gradle引入需要的Jar包:
    compile "com.fasterxml.jackson.core:jackson-databind:2.8.10"
    
    2. 定义一个Login注解
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Login {
    
        String value() default "";
    
    }
    
    
    3. 定义一个MemberDetails.class,用于封装用户信息
    public class MemberDetails {
    
        private String memberId;
    
        private String memberName;
    
        private String memberNickname;
    
        private String memberPhone;
    
        private String memberEmail;
    
    }
    
    
    5. 定义一个会员接口类
    /**
     * @Author: XiongFeng
     * @Description: 会员接口
     * @Date: Created in 19:40 2018/4/10
     */
    public interface MemberService {
    
    	/** 根据TokenId获取用户信息 */
        MemberDto getMemberByToken(String token);
    }
    
    6. 定义一个会员接口实现类,在里面写上用户信息获取方法
    @Service
    public class MemberServiceImpl implements MemberService {
    
        @Override
        public MemberDetails getMemberDetailsByToken(String token) {
            if (StringUtils.isBlank(token)) return null;
            if (!"123".equals(token)) return null;
            MemberDetails memberDetails = new MemberDetails();
            memberDetails.setMemberId("123");
            memberDetails.setMemberName("哈哈123");
            memberDetails.setMemberEmail("seifon@seifon.cn");
            memberDetails.setMemberNickname("Seifon");
            memberDetails.setMemberPhone("13100001111");
            return memberDetails;
        }
    }
    
    7. 定义一个Request请求包装类
    • 通过继承HttpServletRequestWrapper类,重写它里面的多个方法,对前端传过来的参数进行重新封装。因为在Filter,虽然可以通过request.getParameterMap()拿到一个含有参数的map,但是不能直接对request里面东西进行修改操作,一旦重新修改,就会报错。后来我发现j2ee已经给我们提供了解决的办法,使用HttpServletRequestWrapper类来解决向request添加额外参数的功能。于是我对HttpServletRequest进行重新包装,在里面重新定义一个map,将以前的参数put进去,并将我们需要添加的参数放进去,达到我们想要的效果。
    import org.apache.commons.lang.WordUtils;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    import java.util.Enumeration;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Vector;
    
    /**
     * @Author: XiongFeng
     * @Description: 对Request请求重新包装
     * @Date: Created in 11:17 2018/4/13
     */
    public class ParameterRequestWrapper extends HttpServletRequestWrapper {
        
        private Map<String , String[]> params = new HashMap<String, String[]>();
    
    
        @SuppressWarnings("unchecked")
        public ParameterRequestWrapper(HttpServletRequest request) {
            // 将request交给父类,以便于调用对应方法的时候,将其输出,其实父亲类的实现方式和第一种new的方式类似
            super(request);
            //将参数表,赋予给当前的Map以便于持有request中的参数
            this.params.putAll(request.getParameterMap());
        }
        //重载一个构造方法
        public ParameterRequestWrapper(HttpServletRequest request , Map<String , Object> extendParams) {
            this(request);
            addAllParameters(extendParams);//这里将扩展参数写入参数表
        }
    
        /**
         * 复写获取key的方法
         */
        @Override
        public Enumeration getParameterNames() {
            Vector names = new Vector(params.keySet());
            return names.elements();
        }
    
        /**
         * 复写获取值value的方法
         */
        @Override
        public String getParameter(String name) {
            Object v = params.get(name);
            if (v == null) {
                return null;
            } else if (v instanceof String[]) {
                String[] strArr = (String[]) v;
                if (strArr.length > 0) {
                    return strArr[0];
                } else {
                    return null;
                }
            } else if (v instanceof String) {
                return (String) v;
            } else {
                return v.toString();
            }
        }
    
        @Override
        public String[] getParameterValues(String name) {
            Object v = params.get(name);
            if (v == null) {
                return null;
            } else if (v instanceof String[]) {
                return (String[]) v;
            } else if (v instanceof String) {
                return new String[] { (String) v };
            } else {
                return new String[] { v.toString() };
            }
        }
    
        public void addAllParameters(Map<String , Object>otherParams) {//增加多个参数
            for(Map.Entry<String , Object>entry : otherParams.entrySet()) {
                addParameter(entry.getKey() , entry.getValue());
            }
        }
    
    
        public void addParameter(String name , Object value) {//增加参数
            if(value != null) {
                if(value instanceof String[]) {
                    params.put(name , (String[])value);
                }else if(value instanceof String) {
                    params.put(name , new String[] {(String)value});
                }else {
                    params.put(name , new String[] {String.valueOf(value)});
                }
            }
        }
    
        /** 简单封装,请根据需求改进 */
        public void addObject(Object obj) {
            Class<?> clazz = obj.getClass();
            Method[] methods = clazz.getMethods();
            try {
                for (Method method : methods) {
                    if (!method.getName().startsWith("get")) {
                        continue;
                    }
                    Object invoke = method.invoke(obj);
                    if (invoke == null || "".equals(invoke)) {
                        continue;
                    }
    
                    String filedName = method.getName().replace("get", "");
                    filedName = WordUtils.uncapitalize(filedName);
    
                    if (invoke instanceof Collection) {
                        Collection collections = (Collection) invoke;
                        if (collections != null && collections.size() > 0) {
                            String[] strings = (String[]) collections.toArray();
                            addParameter(filedName, strings);
                            return;
                        }
                    }
    
                    addParameter(filedName, invoke);
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    
    }
    
    8. 定义一个过滤器
    • 在这个过滤里面,主要校验Token是否有效以及将会员信息添加到request。首先,从Request请求头中拿到前端传过来的Token,并使用Token调用会员信息获取接口,得到用户的资料,然后将用户信息put到ParameterMap中,这个ParameterMap是我们通过ParameterRequestWrapper重新包装的一个map,因此可以在里面添加会员的参数,然后将新的request传递出去。
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.*;
    import javax.servlet.annotation.WebFilter;
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @Author: XiongFeng
     * @Description: 会员登录信息过滤器
     * @Date: Created in 11:17 2018/4/13
     */
    @Component
    @WebFilter(urlPatterns = "/*")
    public class MemberFilter implements Filter {
    
        MemberService memberService = new MemberServiceImpl();
    
        ObjectMapper objectMapper = new ObjectMapper();
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
    
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    
            HttpServletRequest req = (HttpServletRequest) request;
    
            String tokenId = req.getHeader("X-Authorization");
    
            if (tokenId == null || "".equals(tokenId) || tokenId.isEmpty()) {
                chain.doFilter(request, response);
                return;
            }
    
            MemberDetails memberDetails = memberService.getMemberDetailsByToken(tokenId);
            if (memberDetails == null) this.respFail(response);
    
            ParameterRequestWrapper requestWrapper = new ParameterRequestWrapper(req);
            requestWrapper.addObject(memberDetails);
            chain.doFilter(requestWrapper, response);
        }
    
    	/** 返回失败结果Json数据 */
        private void respFail(ServletResponse response) throws IOException {
            Map<String, Object> map = new HashMap<>();
            map.put("status", 500);
            map.put("message", "登录失效,请登录");
            map.put("data", null);
            String s = objectMapper.writeValueAsString(map);
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            response.getWriter().write(s);
        }
    
        @Override
        public void destroy() {
    
        }
    }
    
    9. 定义一个SpringMVC拦截器
    • 在这个拦截器里面,主要验证Controller方法中是否需要MemberDetails和是否标了@Login注解。首先,从HandlerMethod中获取所有入参,看有没有需要MemberDetails参数,如果有,就从HttpServletRequest中拿memberId,如果不存在说明没有登录,存在就通过。然后HandlerMethod获取@Login注解,判断是否存在,如果存在,就看有没有memberId,没有就不通过。
    package cn.seifon.paymodle.interceptor;
    
    import cn.seifon.paymodle.annotations.Login;
    import cn.seifon.paymodle.dto.MemberDetails;
    import cn.seifon.paymodle.service.manager.member.MemberService;
    import cn.seifon.paymodle.service.manager.member.impl.MemberServiceImpl;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.core.MethodParameter;
    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.lang.reflect.Type;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @Author: XiongFeng
     * @Description: 会员登录信息拦截器
     * @Date: Created in 11:17 2018/4/13
     */
    public class MemberInterceptor extends HandlerInterceptorAdapter {
    
        ObjectMapper objectMapper = new ObjectMapper();
    
        MemberService memberService = new MemberServiceImpl();
    
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
    
            HandlerMethod method = (HandlerMethod) handler;
            String[] memberIds = request.getParameterValues("memberId");
    
            MethodParameter[] methodParameters = method.getMethodParameters();
            //判断方法类是否有MemberDetails入参
            if (methodParameters.length > 0) {
                for (MethodParameter methodParameter : methodParameters) {
                    Type genericParameterType = methodParameter.getGenericParameterType();
                    String typeName = genericParameterType.getTypeName();
                    if (!typeName.equals(MemberDetails.class.getTypeName())) continue;
                    if (memberIds == null || memberIds.length <= 0) return this.respFail(response); //如果找不到用户信息就返回失败
                    break;
                }
            }
    
            //判断是否有Login注解
            Login login = method.getMethodAnnotation(Login.class);
            if (login == null) return true;
    
            if (memberIds == null || memberIds.length <= 0) return this.respFail(response); //如果找不到用户信息就返回失败
            return true;
        }
    
    
    	/** 返回失败结果Json数据 */
        private boolean respFail(HttpServletResponse response) throws IOException {
            Map<String, Object> map = new HashMap<>();
            map.put("status", 500);
            map.put("message", "登录失效,请登录");
            map.put("data", null);
            String s = objectMapper.writeValueAsString(map);
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            response.getWriter().write(s);
            return false;
        }
    
    }
    
    
    10. 将拦截器注册到WebMvcConfigurer中
    @Configuration
    public class MyWebAppConfigurer
            extends WebMvcConfigurerAdapter {
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            // addPathPatterns 用于添加拦截规则
            registry.addInterceptor(new MemberInterceptor()).addPathPatterns("/**");
            super.addInterceptors(registry);
        }
    
    }
    
    11. 定义会员Controller
    • 经过一个过滤器和一个拦截器,request请求终于来到了我们Controller层。这时候,我们只需要在方法里面写入MemberDetails memberDetails 就OK了,不用做任何操作,我们就可以获取会员信息了,是不是炒鸡方便!另外还可以在方法上标@Login注解。
    @RestController
    public class MemberController {
    
    
    	@RequestMapping("/token")
        @Login
        public Map<String, Object> getUser(MemberDetails memberDetails) {
            //User user = userManager.selectByPrimaryKey(id);
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("status", 200);
            map.put("message", "请求成功");
            map.put("data", memberDetails);
            return map;
        }
    	
    }
    

    运行结果:

    遇到的坑:

    • 当时我尝试过把会员参数放到session域中的Attribute,也尝试过在Model里setAttribute。后来发现这是行不通的,在filter中直接使用request.setAttribute()是无效的。放在Modle也是可行,但是Controller里面的方法需要加@ModelAttribute("...")才能得到用户信息,很不方便。唯有通过request.getParameterMap() put()进去,才是最方便的。

    • 一开始我没想到用过滤器,因此我就尝试在拦截器里,直接通过ParameterRequestWrapper对request包装,后来发现不管我怎么弄都不成功。当时非常绝望,后来想了想会不会是拦截器不支持重新包装request,于是我就通过filter去做,没想到成功了。这时,我想既然用到了filter,那干脆直接在filter里面获取@Login注解和获取方法参数得了,后来发现filter里面拿不到方法的信息,哭。后来想到一个办法,可以通过先filter,后拦截器。于是就成功了!

    后记:

    • 这篇文章只是记录了我的一点小小经验,如果有什么不对的地方或者有更好的方法,请大家在评论里留言指正!

    参考文章:http://www.importnew.com/19023.html

    原文链接:http://www.seifon.cn/2018/04/21/利用Filter和拦截器,将用户信息动态传入Request方法/

  • 相关阅读:
    android中接口和抽象类的区别
    最靠谱的禁止ViewPager滑动方法
    Android Studio 自定义属性,命名空间
    代码设置Android EditText的相关问题。输入长度maxLength
    关于Android中,保留小数点后两位的方式
    Android的线程使用来更新UI----Thread、Handler、Looper、TimerTask等
    既然安卓免费,那 Google 是靠什么赚钱的?
    android viewconfiguration
    Android中实现为TextView添加多个可点击的文本
    Textview解析带图片的html示例
  • 原文地址:https://www.cnblogs.com/seifon/p/8975803.html
Copyright © 2011-2022 走看看