拦截器、过滤器、参数读取坑记录

kunzai 2018-07-23

拦截器、过滤器、参数读取坑记录

  目的:做个统一网关接口、app请求要跳到H5的接口复用

主要问题:

   1.H5的登录信息获取 是通过Session 来获取 ,app则是mid(用户id)来获取,信息来源不同

   2.如何区分终端来源是H5还是app

解决思路一:

写一个公用方法,在每一个controller中去调用,判断参数,如果是app的则通过mid获取,H5走session

解决思路二:

   拦截器完成,在preHandle 中统一拦截配置中需要跳转到H5的请求url,统一取登录信息,放入到缓存或者request中,controller只需要从request中或者缓存中获取登录信息不需要再去判断来源

解决如下:
    不管是思路一还是思路二,都会遇到同一个问题,如何取区分app和H5的终端参数,如何取?

1.公用参数里有system(ios、Android)通过这个来区分

2.由于app所有的请求都是post且参数都在body里面,请求头类型为application/json

用request.getParameter(xxx)只能拿到url后面以?xxx=xxx这种拼接格式   或者 请求头类型为 application/x-www-form-urlencoded  这种的。 所以只能用流来解析app传过来的参数。 现有的controller为什么不是流解析,是因为 我们controller里有@RequestBody 来标识绑定body参数,所以我们可以用标识后面的map或者是对象来取。

3.流解析弊端,通俗点就是只能解析一遍,也就是说拦截器中解析一遍拿到systeme参数判断是app还是H5之后,后续controller就没法取到参数值了。原因是因为流解析之后是要关闭的,无法再往下传。所以引入filter 把我们的request重写之后再往下传。

代码:

1.拦截器 CheckCmbMemberInterceptor  主要拦截配置中需要跳转的url,不同的终端不同获取登录对象,统一放到request中。

2.过滤器RequestFilter 主要保存request 信息 用于后续controller操作。后续如果有敏感词过滤,黑白名单,接口开关控制等等, 都可以通过这个filter来实现!

拦截器代码

    springmvc的xml

<mvc:interceptors>
		<mvc:interceptor>
			<mvc:mapping path="/**" />
			<mvc:exclude-mapping path="/error/*.json" />
			<bean
				class="com.xxx.xxx.xxx.interceptor.CheckCmbMemberInterceptor">
				<property name="excludeUrlPattern">
					<list>
						<value>/user/myWallet</value>
					</list>
				</property>
			</bean>
		</mvc:interceptor>
	</mvc:interceptors>

对应的拦截器

CheckCmbMemberInterceptor
package com.xxx.xxx.web.interceptor;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import com.alibaba.dubbo.common.utils.IOUtils;
import com.google.gson.Gson;
import com.xxx.xxx.model.CmbMember;
import com.xxx.xxx.service.core.consumer.QrpayMemberService;
import com.xxx.xxx.utils.BaseResult;
import com.xxx.xxx.utils.ErrorCodeEnum;
import com.xxx.xxx.utils.LogPrintUtil;
import com.xxx.xxx.web.interceptor.req.BaseReq;
import com.xxx.xxx.web.interceptor.req.CustomReq;

/**
 * 登入检查的拦截器。 区分 CmbMember 来源 是H5还是app
 * 
 */
public class CheckCmbMemberInterceptor implements HandlerInterceptor {

	private List<String> excludeUrlPattern;

	private AntPathMatcher urlPatternMatcher = new AntPathMatcher();

	private Gson gson = new Gson();

	private static final String SYSTEM_IOS = "ios";
	private static final String SYSTEM_ANDROID = "android";

	@Autowired
	private QrpayMemberService qrpayMemberService;

	// @Autowired
	// private JedisApi jedisApi;

	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		// 如果没在配置中,直接返回
		for (int i = 0; excludeUrlPattern != null && i < excludeUrlPattern.size(); i++) {
			if (!urlPatternMatcher.match(excludeUrlPattern.get(i), request.getServletPath())) {
				return true;
			}
		}

		// 获取参数判断终端返回
		BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()));
		String reqBody = IOUtils.read(reader);
		BaseReq req = gson.fromJson(reqBody, BaseReq.class);
		LogPrintUtil.printFreeLog("reqId", "CheckCmbMemberInterceptor", "preHandle", "统一拦截器公共req参数", req);

		if (req != null
				&& (SYSTEM_IOS.equalsIgnoreCase(req.getSystem()) || SYSTEM_ANDROID.equalsIgnoreCase(req.getSystem()))) {
			return appHandle(request, response, handler, reqBody);

		} else {

			return h5Handle(request, response, handler);
		}

	}

	private boolean appHandle(HttpServletRequest request, HttpServletResponse response, Object handler, String reqBody)
			throws Exception {

		// 获取用户信息,放入
		CustomReq req = gson.fromJson(reqBody, CustomReq.class);

		if (req.getMid() == null) {
			writeError(request, response, handler, BaseResult.buildFailBaseResult(ErrorCodeEnum.ERROR_402.getCode()));
			return false;
		}

		CmbMember cmbMember = qrpayMemberService.getMemberByMid(Long.valueOf(req.getMid()));
		if (cmbMember == null) {
			writeError(request, response, handler, BaseResult.buildFailBaseResult(ErrorCodeEnum.ERROR_450.getCode()));
			return false;
		}

		request.setAttribute("member", cmbMember);

		return true;

	}

	private void writeError(HttpServletRequest request, HttpServletResponse response, Object handler,
			@SuppressWarnings("rawtypes") BaseResult br) throws Exception {

		OutputStream output = null;
		response.setContentType("application/json");
		response.setCharacterEncoding("UTF-8");
		output = response.getOutputStream();
		byte[] rspJson = new Gson().toJson(br, br.getClass()).getBytes("UTF-8");
		output.write(rspJson);
		output.flush();

	}

	private boolean h5Handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		if (request.getSession() == null) {
			// 由于H5返回的格式各异,不做统一拦截保持原有逻辑 在controller中作返回错误处理
			LogPrintUtil.printFreeLog("reqId", "CheckCmbMemberInterceptor", "H5Handle", "统一拦截器H5",
					"request.getSession()为空");
			return true;
		}
		Object member = request.getSession().getAttribute("member");
		request.setAttribute("member", member);
		return true;
	}

	public void setExcludeUrlPattern(List<String> excludeUrlPattern) {
		this.excludeUrlPattern = excludeUrlPattern;
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
	}
}

 接口controller 调用

@RequestMapping(value = "/myWallet")
	public String myWallet(HttpServletRequest request) throws Exception {
		//查询用户资金信息
		//Object member = request.getSession().getAttribute("member");
		Object member = request.getAttribute("member");
		if(member == null){
			logger.info("当前操作用户session失效,直接跳转登录页面");
			return "/user/login";
		}
		CmbMember cmbMember = (CmbMember)member;
		CmbFundAct cmbFundAct = this.qrpayMemberCommonService.getMemberFund(cmbMember.getMid());
		request.setAttribute("cmbFundAct",cmbFundAct);
		return "user/myWallet";
	}

filter代码

      web.xml声明

<!-- 过滤request请求的Filter -->
	<filter>
		<filter-name>requestFilter</filter-name>
		<filter-class>com.xxx.xxx.web.filter.RequestFilter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>requestFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

 对应的

RequestFilter
package com.xxx.xxx.web.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import com.xxx.xxx.web.interceptor.util.BodyReaderHttpServletRequestWrapper;

/**
 * 过滤器把请求流保存起来 接着传递到controller
 *
 */
public class RequestFilter implements Filter {

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {

	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest httpServletRequest = (HttpServletRequest) request;
		// 防止流读取一次后就没有了, 所以需要将流继续写出去
		ServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(httpServletRequest);
		chain.doFilter(requestWrapper, response);
	}

	@Override
	public void destroy() {

	}

}

 过滤器中重写的

BodyReaderHttpServletRequestWrappe
package com.xxx.xxx.web.interceptor.util;


import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.Enumeration;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

import com.alibaba.dubbo.common.utils.IOUtils;

/***
 * 构造一个 BodyReaderHttpServletRequestWrapper 
 * 重写 HttpServletRequest 
 * 解决 IO读取body参数,后续request为空的情况
 * 
 */
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
    private final byte[] body;

    public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream(), Charset.forName("UTF-8")));
        String reqBody = IOUtils.read(reader);
        body = reqBody.getBytes(Charset.forName("UTF-8"));
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {

        final ByteArrayInputStream bais = new ByteArrayInputStream(body);

        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return bais.read();
            }

			@Override
			public boolean isFinished() {
				// TODO Auto-generated method stub
				return false;
			}

			@Override
			public boolean isReady() {
				// TODO Auto-generated method stub
				return false;
			}

			@Override
			public void setReadListener(ReadListener readListener) {
				// TODO Auto-generated method stub
				
			}
        };
    }

    @Override
    public String getHeader(String name) {
        return super.getHeader(name);
    }

    @Override
    public Enumeration<String> getHeaderNames() {
        return super.getHeaderNames();
    }

    @Override
    public Enumeration<String> getHeaders(String name) {
        return super.getHeaders(name);
    }
}

相关推荐