SpringCloudGateway修改requestBody导致form-data不支持问题解决

本文介绍了如何在Spring Cloud Gateway中优化请求体的修改,特别是处理`multipart/form-data`内容类型的异常问题。通过创建全局过滤器和鉴权过滤器,实现了对请求体的鉴权操作,并确保了对不同内容类型的兼容性。

0.修改requestBody旧方法

0.1 使用 ModifyRequestBodyGatewayFilterFactory

@Slf4j
@Component
public class GlobalFilterImpl implements GlobalFilter, Ordered {

    @Autowired
    GatewayComponent gatewayComponent;

    @Override
    public int getOrder() {
        return -999;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String traceId = MDC.get("traceId");
        exchange.getResponse().getHeaders().add("traceId", traceId);
        ModifyRequestBodyGatewayFilterFactory.Config cf = new ModifyRequestBodyGatewayFilterFactory.Config();
        cf.setRewriteFunction(Object.class, Object.class, gatewayComponent.getRewriteFunction());
        GatewayFilter customerFilter = new ModifyRequestBodyGatewayFilterFactory().apply(cf);
        return customerFilter.filter(exchange, chain);
   
    }

}

0.2 GatewayComponent 鉴权业务

@Slf4j
@Component
public class GatewayComponent {

  public RewriteFunction<Object, Object> getRewriteFunction() {
    return (serverWebExchange, body) -> {
      ServerHttpRequest request = serverWebExchange.getRequest();
		//鉴权逻辑
      LinkedHashMap bodyMap = (LinkedHashMap) body;
      RestContext restContext = new RestContext();
      restContext.setTokenUuid(tokenUuid);
      restContext.setUserId(userId);
      restContext.setProjectId(projectId);
      restContext.setCorpId(corpId);
      restContext.setUserType(userType);
      bodyMap.put("restContext", restContext);
      return Mono.just(bodyMap);
    };
  }

}

以上 存在contentType为multipart/form-data时会报错不支持。

1. 优化后的实现

1.1 全局过滤器 不变

@Slf4j
@Component
public class GlobalFilterImpl implements GlobalFilter, Ordered {

    @Autowired
    GatewayComponent gatewayComponent;

    @Override
    public int getOrder() {
        return -999;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String traceId = MDC.get("traceId");
        exchange.getResponse().getHeaders().add("traceId", traceId);
        return chain.filter(exchange);
    }

}

1.2 新增鉴权过滤器

@Slf4j
@Component
public class TokenFilter implements GlobalFilter, Ordered {

  @Override
  public int getOrder() {
    return -100;
  }

  @Override
  public Mono<Void> filter(
    ServerWebExchange exchange,
    GatewayFilterChain chain
  ) {
    ServerHttpRequest request = exchange.getRequest();
    MediaType mediaType = exchange.getRequest().getHeaders().getContentType();
    if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)) {
      ServerRequest serverRequest = ServerRequest.create(
        exchange,
        HandlerStrategies.withDefaults().messageReaders()
      );
      Mono<LinkedHashMap> modifiedBody = serverRequest
        .bodyToMono(LinkedHashMap.class)
        .flatMap(
          body -> {
            RestContext restContext = new RestContext();
            restContext.setTokenUuid(tokenUuid);
            restContext.setUserId(userId);
            restContext.setProjectId(projectId);
            restContext.setCorpId(corpId);
            restContext.setUserType(userType);
            body.put("restContext", restContext);
            return Mono.just(body);
          }
        );

      BodyInserter bodyInserter = BodyInserters.fromPublisher(
        modifiedBody,
        LinkedHashMap.class
      );
      HttpHeaders headers = new HttpHeaders();
      headers.putAll(exchange.getRequest().getHeaders());
      headers.remove(HttpHeaders.CONTENT_LENGTH);
      CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(
        exchange,
        headers
      );
      return bodyInserter
        .insert(outputMessage, new BodyInserterContext())
        .then(
          Mono.defer(
            () -> {
              ServerHttpRequest decorator = decorate(
                exchange,
                headers,
                outputMessage
              );
              return chain.filter(exchange.mutate().request(decorator).build());
            }
          )
        );
    }
    return chain.filter(exchange);
  }

  ServerHttpRequestDecorator decorate(
    ServerWebExchange exchange,
    HttpHeaders headers,
    CachedBodyOutputMessage outputMessage
  ) {
    return new ServerHttpRequestDecorator(exchange.getRequest()) {
      @Override
      public HttpHeaders getHeaders() {
        long contentLength = headers.getContentLength();
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.putAll(super.getHeaders());
        if (contentLength > 0) {
          httpHeaders.setContentLength(contentLength);
        } else {
          httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
        }
        return httpHeaders;
      }

      @Override
      public Flux<DataBuffer> getBody() {
        return outputMessage.getBody();
      }
    };
  }
}

你提到: > 仍然报错,且浏览器中 `Content-Type: multipart/form-data; boundary=----WebKitFormBoundary0712EHHtbh3D3khL` 这说明: ✅ **前端请求格式是正确的**(使用了 `multipart/form-data`) ❌ **但后端仍然报错 `Content type 'application/octet-stream' not supported`** --- ## 🧠 深入分析:为什么前端正确,后端却收到 `application/octet-stream`? 这个错误说明: > **Spring Boot 接收到了请求,但没有识别出 `multipart/form-data`,而是识别成了 `application/octet-stream`** 这是典型的 **请求被代理或网关错误处理**,或者 **Spring Boot 没有正确解析 multipart 请求**。 --- ## 🔍 常见原因分析 ### 1. **Nginx、网关或反向代理错误处理 multipart 请求** - 有些反向代理会“吃掉”原始的 `multipart/form-data` 类型 - 导致 Spring Boot 接收到的是 `application/octet-stream` ### 2. **Spring Boot 没有启用 MultipartResolver** - Spring Boot 未正确解析 multipart 请求 - 或者使用了 `@RequestBody` 接收 `multipart/form-data`,这是错误的 ### 3. **Spring Boot 版本兼容性问题** - 有些旧版本的 Spring Boot 对 multipart 支持不完善 --- ## ✅ 解决方案一:检查后端是否正确配置 multipart 支持 ### ✅ application.yml 配置(Spring Boot 2.x+) ```yaml spring: servlet: multipart: enabled: true max-file-size: 10MB max-request-size: 10MB ``` 如果你使用的是 **Spring Boot < 2.x**,请使用: ```yaml spring: http: multipart: enabled: true max-file-size: 10MB max-request-size: 10MB ``` --- ## ✅ 解决方案二:确保 Controller 使用 `@RequestPart` 接收 multipart 数据 ### ✅ 正确写法(Java): ```java @PostMapping(path = "/ds/dsbaseinfo/add", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity<String> addDsBaseInfo( @RequestPart("formData") DsBaseInfoEntity formData, @RequestPart("file") MultipartFile file ) { System.out.println("Form Data: " + formData); System.out.println("File Name: " + file.getOriginalFilename()); return ResponseEntity.ok("Success"); } ``` --- ## ✅ 解决方案三:添加 `MultipartResolver` 配置类(Spring Boot 有时会自动配置失败) ```java @Configuration public class MultipartConfig { @Bean public MultipartResolver multipartResolver() { return new StandardServletMultipartResolver(); } } ``` --- ## ✅ 解决方案四:检查是否使用了错误的 Filter 或网关(如 Zuul、Nginx) 如果你使用了: - Nginx 作为反向代理 - Zuul、Spring Cloud Gateway 等网关 - 企业级 API 网关(如 Kong) 请检查这些中间件是否支持并正确转发 `multipart/form-data` 请求。 ### 🔧 Nginx 示例配置(支持文件上传) ```nginx location / { proxy_pass http://your-springboot-app; client_max_body_size 20M; # 保留原始 Content-Type proxy_pass_request_headers on; proxy_set_header Content-Type $http_content_type; } ``` --- ## ✅ 解决方案五:检查是否使用了 `@RequestBody` 错误接收 multipart 数据 ### ❌ 错误写法(不要这样写): ```java @PostMapping("/ds/dsbaseinfo/add") public ResponseEntity<String> addDsBaseInfo(@RequestBody DsBaseInfoEntity formData) { ... } ``` ### ✅ 正确写法(使用 `@RequestPart`): ```java @PostMapping("/ds/dsbaseinfo/add") public ResponseEntity<String> addDsBaseInfo( @RequestPart("formData") DsBaseInfoEntity formData, @RequestPart("file") MultipartFile file ) { ... } ``` --- ## ✅ 浏览器调试建议(Chrome DevTools) 1. 打开 **Network** 2. 查看 `/ds/dsbaseinfo/add` 请求 3. 在 **Headers -> Request Headers** 中确认: - `Content-Type: multipart/form-data; boundary=...` 4. 在 **Headers -> Request Payload** 中查看: - 是否有两个 part:`formData` 和 `file` 5. 在 **Response Headers** 中确认后端是否返回了 415 错误 --- ## ✅ 总结:你该怎么做? | 问题 | 解决方案 | |------|----------| | 后端接收到 `application/octet-stream` | 检查网关、Nginx 是否正确转发 `Content-Type` | | `@RequestPart` 无法接收数据 | 检查类字段是否匹配,是否启用 multipart 配置 | | 415 Unsupported Media Type | 检查是否使用 `@RequestBody` 接收 multipart 数据 | | 文件上传失败 | 检查 `max-file-size` 配置 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值