说一个最近工作中遇到的事,我们系统因为业务发展的问题,针对不同用户群体做了两套系统(B系统和S系统),底层基础功能一样,但偏上层的业务有差异,最近我们想将底层基础功能提供一个统一的入口,所以就新起了一个类似业务网关的服务,把两个系统的接口封装一层,提供一个统一的接口出去,然后B业务的请求转发的B系统,S业务的请求转发到S系统。
但这里就有个很重要的问题了,一个请求进来之后,我们如何判定这个请求应该转发到B系统还是S系统? 当然上游请求的时候可以在请求参数里带上他们的业务来源,我们直接根据业务来源路由即可,实际上最开始我们也是这么做的。 但有个问题时有些接口业务方很难判定自己的请求是属于哪个业务方的,而且未来B和S两个系统也是要做融合的。长期的话,使用者对业务标识字段有很大的理解成本,而且未来融合的时候业务标识会变动,到时候推动上游改造就很难很难了。
幸运的是,我们自己其实可以通过请求中的id信息来判断出是哪个业务的。但是目前已经6-7接口了,未来肯定会继续增加接口,难道每个接口都需要加判断逻辑? 这显然很不程序猿! 上面已经说到了,每个接口都需要传id信息,那是不是只需要写个interceptor把id信息解析出来,然后统一做处理就行了! 实际实现的时候,我发现还是稍微有一点点复杂,特此记录下。
首先如果是将参数放在params里的请求,就很简单了,只需要在Interceptor直接读出来就行了。 形如String orderId = request.getParameter("orderId"); 但从请求body里获取参数就比较复杂了,得从HttpServletRequest里的inputStream里获取到。 但是,java中inputStream的特性,从里面读出内容后,之后就没法读了,也就是说你在Inteceptor里取到了Body,后面的流程就再也取不到了,总之这次的http请求就费了。当然解决方法也很简单,通过我查阅资料,只需要用HttpServletRequestWrapper将Request请求包装一层就行了,作用就是让body能重复读取,具体代码如下:
先来看下RequestWrapper的实现:
public class RequestWrapper extends HttpServletRequestWrapper {private String body;/*** Wrapper的构造方法,主要是将body里的内容取出来,然后存储到对象中的body变量中,方便* 后续复用** @param request The request to wrap* @throws IllegalArgumentException if the request is null*/public RequestWrapper(HttpServletRequest request) throws IOException {super(request);StringBuilder stringBuilder = new StringBuilder();BufferedReader bufferedReader = null;try {InputStream inputStream = request.getInputStream();if (inputStream != null) {bufferedReader = new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));char[] charBuffer = new char[128];int bytesRead = -1;while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {stringBuilder.append(charBuffer, 0, bytesRead);}}} catch (IOException ex) {throw ex;} finally {if (bufferedReader != null) {try {bufferedReader.close();} catch (IOException ex) {throw ex;}}}body = stringBuilder.toString();}/*** 这里才是关键,这里将getInputStream重新,让它能重复获取到body里的内容,这样才不会影响后续的流程* @return* @throws IOException*/@Overridepublic ServletInputStream getInputStream() throws IOException {final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes("UTF-8"));ServletInputStream servletInputStream = new ServletInputStream() {@Overridepublic boolean isFinished() {return false;}@Overridepublic boolean isReady() {return false;}@Overridepublic void setReadListener(ReadListener readListener) { }@Overridepublic int read() throws IOException {return byteArrayInputStream.read();}};return servletInputStream;}/*** 重写获取 字符流的方式* @return* @throws IOException*/@Overridepublic BufferedReader getReader() throws IOException {return new BufferedReader(new InputStreamReader(this.getInputStream(), Charsets.UTF_8));}/*** 获取body* @return*/public String getBody() {return this.body;}
}
上面RequestWrapper的代码我查阅资料的时候在多篇博文中看到了,但是单有RequestWrapper还不足以完成整个请求,而且我看很多网络上的博客都是只在Interceptor中Wapper,但实际这样是不对的,而且也完全不需要,因为必须要替换掉整个请求链路中的Request才行。这里我们只需要在Filter中将普通的Request替换成我们自己的RequestWrapper ,具体代码如下:
@Component
@WebFilter(urlPatterns = "/*", filterName = "wapperRequestFilter")
public class WapperRequestFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {ServletRequest requestWrapper = null;if(servletRequest instanceof HttpServletRequest) {requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);}if(requestWrapper == null) {filterChain.doFilter(servletRequest, servletResponse);} else {// 将请求封装并传递下去filterChain.doFilter(requestWrapper, servletResponse);}}@Overridepublic void destroy() {}
}
接下来我们就可以直接在Inteceptor里使用RequestWrapper来获取Body里的内容,并且不会影响到后续的请求处理,具体代码如下:
@Component
@Slf4j
public class WebInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String orderId = request.getParameter("orderId");String body = StringUtils.EMPTY;if (request instanceof RequestWrapper) {body = ((RequestWrapper) request).getBody();}if (StringUtils.isNotBlank(body)) {JSONObject jsonObject = JSONObject.parseObject(body);orderId = jsonObject.getString("orderId");}LOGGER.info("orderId:{}", orderId);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}
}
在上面的代码中WebInterceptor可以拿到我们替换后的WapperRequest,这个里面可以直接获取到body,而且也不影响后续流程继续获取body。 今天的内容就到这里了,虽然只是自己解决问题的一个小笔记,但也希望能帮助到大家。