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

Spring Boot Filter转换请求格式并添加Form-Data参数失败求助

JSON请求转multipart/form-data并添加固定参数的解决思路

问题核心

  • 需求:将Content-Type: application/json的请求在Filter中转为multipart/form-data,并添加硬编码参数scope=read
  • 报错原因:直接将ContentCachingRequestWrapper强转为MultipartHttpServletRequest,两者无继承/实现关系,类型转换失败

解决步骤

1. 理解类型转换失败的本质

ContentCachingRequestWrapper只是Spring提供的普通HTTP请求缓存包装类,仅用于缓存请求体以便重复读取;而MultipartHttpServletRequest是Spring专门处理multipart请求的接口,只有当请求本身是multipart类型且被MultipartResolver处理后,才会生成该类型实例。要实现JSON转multipart,必须自定义符合要求的请求包装类。

2. 自定义Multipart请求包装类

继承AbstractMultipartHttpServletRequest,将原JSON请求体包装为MultipartFile,同时注入自定义参数:

public class CustomMultipartRequestWrapper extends AbstractMultipartHttpServletRequest {
    private final Map<String, String[]> customParams;
    private final Map<String, MultipartFile> multipartFiles;

    public CustomMultipartRequestWrapper(HttpServletRequest request, String jsonBody) throws IOException {
        super(request);
        // 复制原请求参数,添加自定义参数
        customParams = new HashMap<>(request.getParameterMap());
        customParams.put("scope", new String[]{"read"});

        // 将原JSON请求体包装为MultipartFile
        multipartFiles = new HashMap<>();
        multipartFiles.put("originalJson", new MockMultipartFile(
            "originalJson",
            "originalJson.json",
            "application/json",
            jsonBody.getBytes(request.getCharacterEncoding())
        ));
    }

    // 重写参数相关方法,返回自定义参数集合
    @Override
    public Map<String, String[]> getParameterMap() {
        return customParams;
    }

    @Override
    public String getParameter(String name) {
        String[] values = customParams.get(name);
        return values != null && values.length > 0 ? values[0] : null;
    }

    @Override
    public Enumeration<String> getParameterNames() {
        return Collections.enumeration(customParams.keySet());
    }

    @Override
    public String[] getParameterValues(String name) {
        return customParams.get(name);
    }

    // 重写文件相关方法,返回包装后的JSON文件
    @Override
    public Map<String, MultipartFile> getFileMap() {
        return multipartFiles;
    }

    @Override
    public Iterator<String> getFileNames() {
        return multipartFiles.keySet().iterator();
    }

    @Override
    public MultipartFile getFile(String name) {
        return multipartFiles.get(name);
    }

    @Override
    public List<MultipartFile> getFiles(String name) {
        MultipartFile file = multipartFiles.get(name);
        return file != null ? Collections.singletonList(file) : Collections.emptyList();
    }
}

3. 修改Filter逻辑

在Filter中判断请求类型,对JSON请求进行转换,同时修改Content-Type:

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
    HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;

    HttpServletRequest requestToUse = httpServletRequest;
    String contentType = httpServletRequest.getContentType();

    // 仅对JSON请求进行转换
    if (contentType != null && contentType.contains("application/json")) {
        // 缓存并读取原请求体
        ContentCachingRequestWrapper cachedRequest = new ContentCachingRequestWrapper(httpServletRequest);
        String jsonBody = getStringValue(cachedRequest.getContentAsByteArray(), httpServletRequest.getCharacterEncoding());
        
        // 构造自定义Multipart请求
        CustomMultipartRequestWrapper multipartRequest = new CustomMultipartRequestWrapper(httpServletRequest, jsonBody);
        // 修改Content-Type为multipart/form-data
        multipartRequest.setContentType("multipart/form-data");
        requestToUse = multipartRequest;
    } else {
        // 非JSON请求使用缓存包装类用于日志
        requestToUse = new ContentCachingRequestWrapper(httpServletRequest);
    }

    ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(httpServletResponse);

    filterChain.doFilter(requestToUse, responseWrapper);

    // 原有日志逻辑保持不变
    String responseBody = getStringValue(responseWrapper.getContentAsByteArray(), servletResponse.getCharacterEncoding());
    generateTokenUriLog(responseBody, httpServletRequest, httpServletResponse);
    responseWrapper.copyBodyToResponse();
}

4. 关键注意事项

  • 确保Spring容器中配置了MultipartResolver(如StandardServletMultipartResolver),否则后续处理multipart请求会报错。
  • 避免对已为multipart类型的请求重复转换,防止逻辑冲突。
  • 使用ContentCachingRequestWrapper缓存请求体,确保原请求体可被多次读取(HTTP请求体默认只能读取一次)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 16:25:21