SpringBoot拦截器实战:统一解析GET与POST请求参数的网关路由方案 1. 为什么需要统一处理请求参数最近接手了一个业务网关的改造项目遇到了一个典型的问题。我们有两套业务系统B系统和S系统底层功能相同但上层业务逻辑不同。为了给外部提供统一入口需要开发一个网关服务来路由请求。这时候就面临一个关键问题如何判断请求应该转发到哪个系统最初方案是在请求参数中增加业务标识字段但很快发现这个方案存在明显缺陷。首先业务方有时很难确定自己的请求属于哪个系统其次未来两个系统要合并强制业务方理解这些内部标识会增加迁移成本。更麻烦的是每个接口都要重复编写路由判断逻辑随着接口数量增加维护会越来越困难。2. 拦截器的核心设计思路2.1 请求参数的两种来源在HTTP请求中参数可能出现在两个地方URL查询字符串GET和请求体POST。对于GET请求获取参数很简单String orderId request.getParameter(orderId);但POST请求的参数藏在请求体中处理起来就复杂多了。直接从HttpServletRequest的输入流读取数据会消耗流内容导致后续处理流程无法再次读取。这就像一次性拆开的快递包装 - 拆开后就没法原样复原了。2.2 可重复读取的请求包装器解决方案是使用HttpServletRequestWrapper包装原始请求。这个包装器的核心思路是把请求体内容缓存起来就像把快递里的物品先拿出来拍照存档。当后续流程需要读取时我们提供的是存档副本而不是原始包装。关键实现要点在构造器中一次性读取并存储请求体内容重写getInputStream()方法返回基于缓存内容的流重写getReader()方法确保字符流读取也能正常工作public class RequestWrapper extends HttpServletRequestWrapper { private String body; public RequestWrapper(HttpServletRequest request) throws IOException { super(request); // 读取并存储请求体内容 StringBuilder sb new StringBuilder(); try (BufferedReader reader request.getReader()) { char[] buffer new char[128]; int bytesRead; while ((bytesRead reader.read(buffer)) 0) { sb.append(buffer, 0, bytesRead); } } body sb.toString(); } Override public ServletInputStream getInputStream() { ByteArrayInputStream byteArray new ByteArrayInputStream(body.getBytes()); return new ServletInputStream() { // 实现必要的方法 }; } }3. 完整的实现方案3.1 过滤器层的请求包装仅仅在拦截器中包装请求是不够的必须在过滤器层就完成请求的替换。这是因为Servlet规范中请求体只能在过滤器链中读取一次。我们实现一个Filter将原始请求替换为我们的RequestWrapperComponent WebFilter(urlPatterns /*) public class RequestWrappingFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (request instanceof HttpServletRequest) { HttpServletRequest httpRequest (HttpServletRequest) request; // 只包装非multipart请求 if (!httpRequest.getContentType().contains(multipart/form-data)) { request new RequestWrapper(httpRequest); } } chain.doFilter(request, response); } }注意处理multipart请求的特殊情况这类请求通常用于文件上传不适合用我们的包装器处理。3.2 拦截器中的统一处理有了可重复读取的请求包装器拦截器中的处理就变得简单了Component public class RoutingInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String id request.getParameter(id); // 先尝试从URL参数获取 if (StringUtils.isBlank(id) request instanceof RequestWrapper) { String body ((RequestWrapper) request).getBody(); JSONObject json JSONObject.parseObject(body); id json.getString(id); // 从请求体获取 } if (StringUtils.isNotBlank(id)) { RouteContext.setCurrentSystem(determineSystem(id)); } return true; } private String determineSystem(String id) { // 根据ID特征判断所属系统 return id.startsWith(B) ? B系统 : S系统; } }这里我们建立了一个路由上下文后续的业务逻辑可以直接从中获取系统标识完全不需要关心路由判断逻辑。4. 实际应用中的注意事项4.1 性能优化考虑每次请求都要完整读取和缓存请求体这对性能有一定影响。可以通过以下方式优化只包装需要读取请求体的请求根据URL模式判断对大文件上传等特殊请求跳过包装设置合理的请求体大小限制防止内存溢出// 在过滤器中增加条件判断 if (httpRequest.getContentLength() MAX_BODY_SIZE) { chain.doFilter(request, response); return; }4.2 异常处理机制完善的异常处理是健壮性的保证请求体解析失败时应该记录详细日志对于格式错误的JSON应该返回明确的错误信息考虑添加请求耗时监控及时发现性能问题try { JSONObject json JSONObject.parseObject(body); } catch (JSONException e) { log.error(Failed to parse request body: {}, body, e); response.sendError(HttpStatus.BAD_REQUEST.value(), Invalid JSON format); return false; }4.3 与现有框架的集成如果你的项目使用了Spring Cloud Gateway等API网关可以考虑将这套逻辑实现在全局过滤器中。对于Spring MVC应用还可以考虑使用ControllerAdvice实现更精细的控制。在微服务架构中这套方案可以扩展为在网关层统一提取关键参数通过请求头或上下文将参数传递给下游服务下游服务直接使用预处理的参数避免重复解析5. 方案效果与扩展思考这套方案在我们的网关服务中运行良好带来了几个明显好处业务代码完全解耦路由逻辑新增接口只需关注业务实现路由规则变更只需修改拦截器不影响现有接口统一了参数解析逻辑避免不同开发人员实现不一致未来可能的扩展方向结合配置中心实现动态路由规则支持更复杂的参数映射规则添加请求参数校验和转换功能在实际项目中我们后来又扩展了这套方案添加了对JWT令牌的自动解析和权限校验功能。所有这些都是通过拦截器统一处理业务代码始终保持简洁。这种架构模式特别适合需要统一处理横切关注点的场景。