springboot的handlerInterceptor中获取get和post请求参数

说一个最近工作中遇到的事,我们系统因为业务发展的问题,针对不同用户群体做了两套系统(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 的实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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
	 */
	@Override
	public ServletInputStream getInputStream() throws IOException {
		final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes("UTF-8"));
		ServletInputStream servletInputStream = new ServletInputStream() {
			@Override
			public boolean isFinished() {
				return false;
			}

			@Override
			public boolean isReady() {
				return false;
			}

			@Override
			public void setReadListener(ReadListener readListener) { }

			@Override
			public int read() throws IOException {
				return byteArrayInputStream.read();
			}
		};
		return servletInputStream;
	}


	/**
	 * 重写获取 字符流的方式
	 * @return
	 * @throws IOException
	 */
	@Override
	public 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 ,具体代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Component
@WebFilter(urlPatterns = "/*", filterName = "wapperRequestFilter")
public class WapperRequestFilter implements Filter {

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

	}

	@Override
	public 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);
		}
	}

	@Override
	public void destroy() {

	}
}

接下来我们就可以直接在 Inteceptor 里使用 RequestWrapper 来获取 Body 里的内容,并且不会影响到后续的请求处理,具体代码如下:

@Component @Slf4j public class WebInterceptor implements HandlerInterceptor { @Override public 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; }

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

}    在上面的代码中 WebInterceptor 可以拿到我们替换后的 WapperRequest,这个里面可以直接获取到 body,而且也不影响后续流程继续获取 body。 今天的内容就到这里了,虽然只是自己解决问题的一个小笔记,但也希望能帮助到大家。 ————————————————

Licensed under CC BY-NC-SA 4.0
最后更新于 Jan 06, 2025 05:52 UTC
comments powered by Disqus
Built with Hugo
主题 StackJimmy 设计
Caret Up