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

Flutter使用MultipartRequest发送JSON与MultipartFile报415错误排查

问题描述

通过Flutter的MultipartRequest向SpringBoot API发送包含图片(MultipartFile)和JSON对象的多部分请求,Postman和React端请求正常,但Flutter端始终返回415 UNSUPPORTED_MEDIA_TYPE错误,排除请求头问题,推测与Content-Type识别有关,需排查修复。

Flutter 原代码

Future<dynamic> postMultipartChetada(String url, List<XFile> files, dynamic data, String dataName) async {
    late LocalStorageService _localStorageService;
    GetIt.I
        .getAsync<LocalStorageService>()
        .then((value) => _localStorageService = value);
    final request = http.MultipartRequest(
      'POST',
      Uri.parse(ApiConstants.baseUrl + url),
      
    );
    if(files.isNotEmpty){
      for (final file in files) {
      final bytes = await file.readAsBytes();
      request.files.add(http.MultipartFile.fromBytes(
        'file',
        bytes,
        filename: file.name.substring(1, 10),
      ));
    }

    }
    request.fields[dataName] = jsonEncode(data.toJson());
    var boundary = Uuid().v4();
    final headers = {
      'Authorization': 'Bearer ${_localStorageService.getFromDisk("user_token")}',
      'Content-Type': 'multipart/form-data; boundary=$boundary',
      "Accept": "*/*",
    };
    request.headers.addAll(headers);

    try {
      final response = await request.send();
      return response;
    } catch (error) {
      throw new Exception("Error en el cliente");
    }
  }

SpringBoot 请求日志

2023-05-29 22:50:33.344 DEBUG 27288 --- [io-8080-exec-10] o.s.web.servlet.DispatcherServlet        : POST "/question", parameters={multipart}
2023-05-29 22:50:33.383 DEBUG 27288 --- [io-8080-exec-10] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.triana.salesianos.HazTuHuertoAPI.controller.QuestionController#register(MultipartFile, CreateQuestion, User)
2023-05-29 22:50:33.386 DEBUG 27288 --- [io-8080-exec-10] o.s.web.method.HandlerMethod             : Could not resolve parameter [1] in public org.springframework.http.ResponseEntity<com.triana.salesianos.HazTuHuertoAPI.model.dto.question.QuestionDetails> com.triana.salesianos.HazTuHuertoAPI.controller.QuestionController.register(org.springframework.web.multipart.MultipartFile,com.triana.salesianos.HazTuHuertoAPI.model.dto.question.CreateQuestion,com.triana.salesianos.HazTuHuertoAPI.model.User): Content type 'application/octet-stream' not supported
2023-05-29 22:50:33.387 DEBUG 27288 --- [io-8080-exec-10] .m.m.a.ExceptionHandlerExceptionResolver : Using @ExceptionHandler com.triana.salesianos.HazTuHuertoAPI.error.GlobalRestControllerAdvice#handleException(Exception, WebRequest)
2023-05-29 22:50:33.401 DEBUG 27288 --- [io-8080-exec-10] o.s.w.s.m.m.a.HttpEntityMethodProcessor  : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/json, application/*+json]
2023-05-29 22:50:33.413 DEBUG 27288 --- [io-8080-exec-10] o.s.w.s.m.m.a.HttpEntityMethodProcessor  : Writing [ApiErrorImpl(status=415 UNSUPPORTED_MEDIA_TYPE, message=Content type 'application/octet-stream' not  (truncated)...]
2023-05-29 22:50:33.416 DEBUG 27288 --- [io-8080-exec-10] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/octet-stream' not supported]
2023-05-29 22:50:33.417 DEBUG 27288 --- [io-8080-exec-10] o.s.web.servlet.DispatcherServlet        : Completed 415 UNSUPPORTED_MEDIA_TYPE
解决方案

从日志看,错误核心是Spring无法解析JSON参数:Flutter将JSON字段放到fields中时,默认Content-Type为application/x-www-form-urlencoded,但Spring期望的是application/json;同时手动设置Content-Type和boundary会与MultipartRequest自动生成的内容冲突,还有异步获取token的潜在问题。

修复步骤

  1. 移除手动设置的Content-Type:MultipartRequest会自动生成正确的multipart/form-data请求头和随机boundary,手动设置会导致格式混乱。
  2. 将JSON数据作为MultipartFile添加:把JSON字符串包装成application/json类型的MultipartFile,而非放到fields里,匹配Spring的解析要求。
  3. 同步获取LocalStorageService:用await替代then,避免token未初始化就被使用的情况。

修正后的Flutter代码

Future<dynamic> postMultipartChetada(String url, List<XFile> files, dynamic data, String dataName) async {
    // 同步获取LocalStorageService,确保token已加载
    final _localStorageService = await GetIt.I.getAsync<LocalStorageService>();
    
    final request = http.MultipartRequest(
      'POST',
      Uri.parse(ApiConstants.baseUrl + url),
    );

    // 添加图片文件,可选指定图片Content-Type
    if(files.isNotEmpty){
      for (final file in files) {
        final bytes = await file.readAsBytes();
        request.files.add(http.MultipartFile.fromBytes(
          'file',
          bytes,
          filename: file.name.substring(1, 10),
          contentType: MediaType('image', file.name.split('.').last),
        ));
      }
    }

    // 将JSON数据以application/json类型的MultipartFile添加
    final jsonBytes = utf8.encode(jsonEncode(data.toJson()));
    request.files.add(http.MultipartFile.fromBytes(
      dataName,
      jsonBytes,
      filename: '$dataName.json',
      contentType: MediaType('application', 'json'),
    ));

    // 添加必要请求头
    final headers = {
      'Authorization': 'Bearer ${_localStorageService.getFromDisk("user_token")}',
      "Accept": "*/*",
    };
    request.headers.addAll(headers);

    try {
      final response = await request.send();
      return response;
    } catch (error) {
      throw Exception("Error en el cliente");
    }
  }

额外说明

  • 若Spring接口中JSON参数用@RequestPart注解接收,上述修改完全适配;若用@RequestBody,需确保JSON部分的Content-Type正确。
  • 为图片指定对应Content-Type,可帮助Spring更准确识别文件类型,避免潜在解析问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 12:29:58