Spring MVC POST 请求包含多部分文件和其他 dto 的 dto

Spring MVC POST request with dto that contains multipart files and other dtos

我有一个包含其他 DTO 和多部分文件列表的 DTO。我正在尝试处理该 DTO,但我似乎无法读取请求。

class TeacherDTO {
   private SpecializationDto specializationDto;
   private List<MultipartFile> files;
}

@PostMapping(consumes = {MediaType.MULTIPART_FORM_DATA_VALUE},
            produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<Object> saveNewTeacher(@ModelAttribute @Valid TeacherDTO teacherDto){

//process request

}

从 Swagger UI 创建示例请求时,出现以下异常:

type 'java.lang.String' to required type 'SpecializationDto' for property 'specializationDto': no matching editors or conversion strategy found

如果我使用@RequestBody 而不是@ModelAttribute,那么我得到

Content type 'multipart/form-data;boundary=----WebKitFormBoundaryVEgYwEbpl1bAOjAs;charset=UTF-8' not supported]

Swagger 依赖项:

<dependency>
   <groupId>org.springdoc</groupId>
   <artifactId>springdoc-openapi-ui</artifactId>
   <version>1.5.2</version>
</dependency>
<dependency>
   <groupId>org.springdoc</groupId>
   <artifactId>springdoc-openapi-data-rest</artifactId>
   <version>1.5.2</version>
</dependency>

OpenAPI3.0 配置:



@Configuration
public class OpenApi30Config {

  private final String moduleName;
  private final String apiVersion;

  public OpenApi30Config(
      @Value("${spring.application.name}") String moduleName,
      @Value("${api.version}") String apiVersion) {
    this.moduleName = moduleName;
    this.apiVersion = apiVersion;
  }

  @Bean
  public OpenAPI customOpenAPI() {
    final var securitySchemeName = "bearerAuth";
    final var apiTitle = String.format("%s API", StringUtils.capitalize(moduleName));
    return new OpenAPI()
        .addSecurityItem(new SecurityRequirement().addList(securitySchemeName))
        .components(
            new Components()
                .addSecuritySchemes(securitySchemeName,
                    new SecurityScheme()
                        .name(securitySchemeName)
                        .type(SecurityScheme.Type.HTTP)
                        .scheme("bearer")
                        .bearerFormat("JWT")
                )
        )
        .info(new Info().title(apiTitle).version(apiVersion));
  }
}

这似乎是 springdoc-openapi-ui builds 表单数据请求的问题。我能够重现这一点并注意到它发送了一个多部分请求(通过浏览器的开发工具拦截):

-----------------------------207598777410513073071314493349
Content-Disposition: form-data; name="specializationDto"\r\n\r\n{\r\n  "something": "someValue"\r\n}


-----------------------------207598777410513073071314493349
Content-Disposition: form-data; name="files"; filename="somefile.txt"
Content-Type: application/octet-stream

<content>

-----------------------------207598777410513073071314493349
Content-Disposition: form-data; name="files"; filename="somefile.txt"
Content-Type: application/octet-stream

<content>

使用该负载 Spring 无法反序列化 specializationDto,导致您观察到的“未找到匹配的编辑器或转换策略”异常。但是,如果您通过邮递员或 curl 发送请求(请注意 specializationDto 对象的点符号)

curl --location --request POST 'http://localhost:8080/upload' \
--form 'files=@"/path/to/somefile"' \
--form 'files=@"/path/to/somefile"' \
--form 'specializationDto.something="someValue"'

然后 Spring 能够正确解析它。这是我的休息映射,它将按预期记录以下内容:

    @RequestMapping(value = "/upload", method = RequestMethod.POST, 
                    consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
    public void upload(@ModelAttribute TeacherDto requestDto) {
        System.out.println(requestDto);
    }


// logs:
TeacherDto(specializationDto=SpecializationDto(something=someValue), files=[org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@78186ea6, org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@461c9cbc])

我建议您在他们的 github page.

上打开一个错误

编辑:

OP开github工单后,部分作者反馈如下:

[...] With spring, you can use @RequestPart spring annotation to describe the different parts, with the related encoding media type. Note that there is a limitation with the current swagger-ui implementation as the encoding attribute is not respected on the request.[...]

他们还提供了一个可能的解决方法,如下所示:

@PostMapping(consumes =  MediaType.MULTIPART_FORM_DATA_VALUE,
        produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> saveNewTeacher( @RequestPart(value = "specializationDto") @Parameter(schema =@Schema(type = "string", format = "binary"))  final SpecializationDto specializationDto,
        @RequestPart(value = "files")  final List<MultipartFile> files){
    return null;
}