You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring接收application/x-www-form-urlencoded请求如何仅解码key并保留参数顺序

问题原因

Spring默认的FormHttpMessageConverter处理application/x-www-form-urlencoded类型的请求时,若用@RequestBody String直接接收请求体,会先把表单参数解析为MultiValueMap,再将所有键值对重新做URL编码后拼接为字符串返回,这就导致key被强制编码,同时原始value的编码也可能被破坏。

实现方案

方案1:直接读取原始请求体自行解析(兼容性最好,无需额外配置)

直接通过HttpServletRequest读取未被Spring修改的原始请求体,自行拆分解析,仅对key做解码,完全保留value原始内容和参数顺序:

  1. 修改接口定义,改用HttpServletRequest作为入参:
@PostMapping(path = "/some-api", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
public void processWebhook(HttpServletRequest request) throws IOException {
    // 读取原始请求体内容
    String rawBody = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
    // 解析获取参数,顺序和原始请求完全一致
    LinkedHashMap<String, String> params = parseRawFormBody(rawBody, StandardCharsets.UTF_8);
    // 后续业务逻辑
}
  1. 实现仅解码key的解析方法,用LinkedHashMap保证参数顺序:
private LinkedHashMap<String, String> parseRawFormBody(String rawBody, Charset charset) {
    LinkedHashMap<String, String> paramMap = new LinkedHashMap<>();
    if (rawBody == null || rawBody.isBlank()) {
        return paramMap;
    }
    // 按参数分隔符&拆分
    String[] paramPairs = rawBody.split("&");
    for (String pair : paramPairs) {
        if (pair.isBlank()) {
            continue;
        }
        // 仅拆分第一个=,避免value中包含=时被误拆分
        int eqIndex = pair.indexOf('=');
        if (eqIndex == -1) {
            // 无value的参数
            String decodedKey = URLDecoder.decode(pair, charset);
            paramMap.put(decodedKey, "");
        } else {
            String encodedKey = pair.substring(0, eqIndex);
            String rawValue = pair.substring(eqIndex + 1);
            // 仅对key做URL解码,value保留原始编码
            String decodedKey = URLDecoder.decode(encodedKey, charset);
            paramMap.put(decodedKey, rawValue);
        }
    }
    return paramMap;
}

方案2:自定义消息转换器(适配原有@RequestBody写法)

如果不想修改原有接口的入参定义,可以自定义消息转换器,注册到Spring MVC中自动完成解析:

  1. 自定义HttpMessageConverter实现类:
public class RawFormKeyDecodeConverter extends AbstractHttpMessageConverter<Map<String, String>> {
    public RawFormKeyDecodeConverter() {
        super(MediaType.APPLICATION_FORM_URLENCODED);
    }

    @Override
    protected boolean supports(Class<?> clazz) {
        return Map.class.isAssignableFrom(clazz);
    }

    @Override
    protected Map<String, String> readInternal(Class<? extends Map<String, String>> clazz, HttpInputMessage inputMessage) throws IOException {
        String rawBody = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8);
        // 调用方案1中的parseRawFormBody方法完成解析
        return parseRawFormBody(rawBody, StandardCharsets.UTF_8);
    }

    @Override
    protected void writeInternal(Map<String, String> stringStringMap, HttpOutputMessage outputMessage) {
        throw new UnsupportedOperationException("仅用于接收表单参数,无需实现序列化逻辑");
    }
}
  1. 将自定义转换器注册到Spring MVC的转换器列表最前面,确保优先生效:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(0, new RawFormKeyDecodeConverter());
    }
}
  1. 接口直接用LinkedHashMap接收即可:
@PostMapping(path = "/some-api", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
public void processWebhook(@RequestBody LinkedHashMap<String, String> params) {
    // 直接使用解析好的参数即可
}

内容的提问来源于stack exchange,提问作者MilindaD

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 22:54:05