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的潜在问题。
修复步骤
- 移除手动设置的Content-Type:
MultipartRequest会自动生成正确的multipart/form-data请求头和随机boundary,手动设置会导致格式混乱。 - 将JSON数据作为MultipartFile添加:把JSON字符串包装成
application/json类型的MultipartFile,而非放到fields里,匹配Spring的解析要求。 - 同步获取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
相关产品推荐
相关产品推荐

