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); } }