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