优化 POST 参数解析,使动态解析参数
当 POST 方法试图再次读取请求体,但是请求体已经被 Spring 的 @RequestBody 注解消费了。
我们需要在 @RequestBody 的同时,优化参数解析方法,使其能够动态地从请求中读取其他的额外参数,而不需要预先定义固定的字段。
以下是一个改进的方案:
- 首先,我们创建一个自定义的注解来标记我们想要处理的请求:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ProcessRequestParams {
}
- 然后,我们创建一个
RequestBodyWrapper
类来包装原始的请求体:
public class RequestBodyWrapper {
private final Map<String, Object> body;
public RequestBodyWrapper(Map<String, Object> body) {
this.body = body;
}
public Map<String, Object> getBody() {
return body;
}
}
- 为了确保 ContentCachingRequestWrapper 被正确应用,你需要在你的应用中添加一个 Filter:
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ContentCachingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
filterChain.doFilter(wrappedRequest, response);
}
}
- 接下来,我们创建一个自定义的
HandlerMethodArgumentResolver
:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.netease.mdas.system.bean.RequestBodyWrapper;
import org.springframework.core.MethodParameter;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.WebUtils;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
public class RequestBodyWrapperArgumentResolver implements HandlerMethodArgumentResolver {
private final ObjectMapper objectMapper;
public RequestBodyWrapperArgumentResolver(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(RequestBodyWrapper.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest nativeRequest = (HttpServletRequest) webRequest.getNativeRequest();
String body = getRequestBody(nativeRequest);
if (body != null && !body.isEmpty()) {
Map<String, Object> map = objectMapper.readValue(body, Map.class);
return new RequestBodyWrapper(map);
}
return new RequestBodyWrapper(null);
}
private String getRequestBody(HttpServletRequest request) throws IOException {
ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
if (wrapper != null) {
byte[] buf = wrapper.getContentAsByteArray();
if (buf.length > 0) {
return new String(buf, StandardCharsets.UTF_8);
}
}
// 如果不是 ContentCachingRequestWrapper,则直接读取输入流
return StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
}
}
- 在 Spring 配置中注册这个
ArgumentResolver
:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private ObjectMapper objectMapper;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new RequestBodyWrapperArgumentResolver(objectMapper));
}
}
- 修改
SearchController
:
@ApiOperation(value = "邮件列表", notes = "邮件列表,根据条件名模糊查询")
@PostMapping("/findList")
@ProcessRequestParams
public ResponseBean findList(@RequestHeader(value = "systemproduct") String orgId,
@RequestBody FeedbackSearchVO dto,
RequestBodyWrapper wrapper) {
// ... 其他代码保持不变 ...
dto.setOtherInfoQuery(initOtherInfoQuery(wrapper.getBody()));
// ... 其他代码保持不变 ...
}
- 最后,修改
initOtherInfoQuery
方法:
public static Map<String, List<JSONObject>> initOtherInfoQuery(Map<String, Object> requestBody) {
Map<String, List<JSONObject>> otherInfoQuery = new HashMap<>();
for (Map.Entry<String, Object> entry : requestBody.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof List) {
List<JSONObject> jsonList = new ArrayList<>();
for (Object item : (List<?>) value) {
jsonList.add(new JSONObject(item));
}
otherInfoQuery.put(key, jsonList);
} else {
List<JSONObject> jsonList = new ArrayList<>();
jsonList.add(new JSONObject(value));
otherInfoQuery.put(key, jsonList);
}
}
return otherInfoQuery;
}
这种方法的优点是:
- 它可以动态处理所有请求参数,不需要预先定义固定的字段。
- 它不会干扰
@RequestBody
的正常工作。 - 它允许你同时访问解析后的 DTO 和原始请求体。
这个解决方案应该能够处理动态的参数,而不需要在代码中硬编码这些参数名。
相关文章