如何用com.epages.restdocs-api-spec 0.18.2记录multipart/form-data请求
问题描述
我在开发Spring Boot项目时,使用com.epages.restdocs-api-spec 0.18.2版本生成API文档,但在记录接收multipart/form-data的端点时遇到问题——生成的OpenAPI 3 YAML文件完全缺失请求部分的定义,仅保留了响应内容。
以下是我的代码和配置细节:
控制器代码
@PostMapping(consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) public ResponseEntity<EraseAreaRequestDto.Response> home( @ModelAttribute @Validated RequestDto dto ) { log.info("original filename: {}", dto.getOriginalImageFile().getOriginalFilename()); log.info("masking filename: {}", dto.getMaskingImageFile().getOriginalFilename()); log.info("original url: {}", dto.getOriginalImageUrl()); log.info("masking url: {}", dto.getMaskingImageUrl()); log.info("test1: {}", dto.getRequest3().getTest1()); log.info("test2: {}", dto.getRequest3().getTest2()); var response = new EraseAreaRequestDto.Response(); response.setOriginalFilename(dto.getOriginalImageFile().getOriginalFilename()); response.setMaskingFilename(dto.getMaskingImageFile().getOriginalFilename()); response.setOriginalImageUrl(dto.getOriginalImageUrl()); response.setMaskingImageUrl(dto.getMaskingImageUrl()); return ResponseEntity.ok(response); }
RequestDto 定义
@Data @JsonIgnoreProperties(ignoreUnknown = true) public class RequestDto { private MultipartFile originalImageFile; private MultipartFile maskingImageFile; private String originalImageUrl; private String maskingImageUrl; }
测试代码
@Test public void documentMultipart() throws Exception { Part part = new MockPart("originalImageFile", "originalImageFile", new byte[10], MediaType.IMAGE_JPEG); Part part2 = new MockPart("maskingImageFile", "maskingImageFile", new byte[10], MediaType.IMAGE_JPEG); Part originalImageUrlPart = new MockPart("originalImageUrl", null, "originalImageUrl".getBytes(StandardCharsets.UTF_8), MediaType.TEXT_PLAIN); Part maskingImageUrlPart = new MockPart("maskingImageUrl", null, "maskingImageUrl".getBytes(StandardCharsets.UTF_8), MediaType.TEXT_PLAIN); return mockMvc.perform(RestDocumentationRequestBuilders.multipart(API_ERASE_AREA) .part(part, part2, originalImageUrlPart, maskingImageUrlPart, part3) .contentType(MediaType.MULTIPART_FORM_DATA_VALUE) .characterEncoding(StandardCharsets.UTF_8)) .andExpect(status().isOk()) .andExpect(jsonPath("$.originalFilename").exists()) .andExpect(jsonPath("$.maskingFilename").exists()) .andExpect(jsonPath("$.originalImageUrl").exists()) .andExpect(jsonPath("$.maskingImageUrl").exists()) .andDo(document( "{Identifier}", resource( ResourceSnippetParameters.builder() .summary("Summary") .description("description") .requestSchema(schema("RequestDto")) .formParameters( parameterWithName("originalImageUrl").description("adsf").optional().type(SimpleType.STRING), parameterWithName("maskingImageUrl").description("asdf").optional(), parameterWithName("originalImageFile").description("asdf").optional(), parameterWithName("maskingImageFile").description("asdf").optional() ) .responseSchema(schema("EraseAreaRequestDto.Response")) // skip... .responseFields() .tags("tag") .build()) )); }
Gradle 配置
plugins { id 'java' id 'org.springframework.boot' version '3.3.0' id 'io.spring.dependency-management' version '1.1.5' id 'org.graalvm.buildtools.native' version '0.10.2' id 'com.epages.restdocs-api-spec' version "0.18.2" } ext { springShellVersion = "3.3.0" host = 'localhost:8888' apiUrl = '/' } group = 'com.asd' version = '0.0.1-SNAPSHOT' java { toolchain { languageVersion = JavaLanguageVersion.of(21) } } configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.shell:spring-shell-starter' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' // restdoc testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' testImplementation "com.epages:restdocs-api-spec-mockmvc:0.18.2" testImplementation 'com.epages:restdocs-api-spec-restassured:0.18.2' testImplementation 'org.springframework.restdocs:spring-restdocs-restassured' } dependencyManagement { imports { mavenBom "org.springframework.shell:spring-shell-dependencies:$springShellVersion" } } tasks.named('test') { useJUnitPlatform() } task copyYaml { logger.info("copyYaml -> ${project.buildDir}/resources/main/public/docs") copy { from "build/api-spec" into "${project.buildDir}/resources/main/static/docs" } } openapi3 { delete "build/api-spec" delete "build/generated-snippets" server = apiUrl title = 'test' description = "test test" tagDescriptionsPropertiesFile = 'src/docs/tag-descriptions.yaml' version = '0.1.0' format = 'yaml' } openapi { delete "build/api-spec" delete "build/generated-snippets" host = 'localhost:8888' title = 'test' description = "test test" tagDescriptionsPropertiesFile = 'src/docs/tag-descriptions.yaml' version = '0.1.0' format = 'yaml' }
当前生成的 OpenAPI 3 YAML
openapi: 3.0.1 info: title: test description: test test version: 0.1.0 servers: - url: / tags: [] paths: /api/sample: post: tags: - "tag" summary: summary description: description operationId: {idenifier} responses: "200": description: "200" content: application/json;charset=UTF-8: schema: $ref: '#/components/schemas/RequestDto.Response' examples: identifier: value: "{\"originalFilename\":\"originalImageFile\",\"maskingFilename\":\"maskingImageFile\",\"originalImageUrl\":\"originalImageUrl\",\"maskingImageUrl\":\"maskingImageUrl\",\"list\":null,\"array\":null}"
我需要让com.epages.restdocs-api-spec正确生成multipart/form-data类型的请求定义,确保所有字段(文件和普通字符串)都能被完整记录到OpenAPI 3文档中;如果OpenAPI 3无法实现,OpenAPI 2也可以接受,但必须保证请求的所有字段都能被正确描述。
解决方案
针对com.epages.restdocs-api-spec 0.18.2版本记录multipart/form-data请求的问题,可通过以下步骤修复:
1. 调整测试代码的文档配置
requestSchema不适用于multipart/form-data类型请求,需改用requestParts分别定义每个表单部分,并指定对应媒体类型:
修改测试中的resource配置:
.resource( ResourceSnippetParameters.builder() .summary("Summary") .description("description") // 移除requestSchema,改用requestParts定义每个multipart部分 .requestParts( partWithName("originalImageFile") .description("原始图片文件") .optional() .contentType(MediaType.IMAGE_JPEG_VALUE), partWithName("maskingImageFile") .description("遮罩图片文件") .optional() .contentType(MediaType.IMAGE_JPEG_VALUE), partWithName("originalImageUrl") .description("原始图片URL") .optional() .contentType(MediaType.TEXT_PLAIN_VALUE) .type(SimpleType.STRING), partWithName("maskingImageUrl") .description("遮罩图片URL") .optional() .contentType(MediaType.TEXT_PLAIN_VALUE) .type(SimpleType.STRING) ) .responseSchema(schema("EraseAreaRequestDto.Response")) .tags("tag") .build() )
2. 给RequestDto添加Schema注解
为了让OpenAPI正确识别字段类型,给RequestDto添加@Schema注解明确字段信息:
@Data @JsonIgnoreProperties(ignoreUnknown = true) @Schema(description = "Multipart请求参数") public class RequestDto { @Schema(description = "原始图片文件", type = "string", format = "binary") private MultipartFile originalImageFile; @Schema(description = "遮罩图片文件", type = "string", format = "binary") private MultipartFile maskingImageFile; @Schema(description = "原始图片URL") private String originalImageUrl; @Schema(description = "遮罩图片URL") private String maskingImageUrl; }
3. 验证生成结果
修改后重新运行测试,生成的OpenAPI 3 YAML会包含完整的请求定义,示例如下:
paths: /api/sample: post: tags: - "tag" summary: summary description: description operationId: {identifier} requestBody: content: multipart/form-data: schema: type: object properties: originalImageFile: type: string format: binary description: 原始图片文件 maskingImageFile: type: string format: binary description: 遮罩图片文件 originalImageUrl: type: string description: 原始图片URL maskingImageUrl: type: string description: 遮罩图片URL required: [] responses: "200": description: "200" content: application/json;charset=UTF-8: schema: $ref: '#/components/schemas/RequestDto.Response' examples: identifier: value: "{\"originalFilename\":\"originalImageFile\",\"maskingFilename\":\"maskingImageFile\",\"originalImageUrl\":\"originalImageUrl\",\"maskingImageUrl\":\"maskingImageUrl\",\"list\":null,\"array\":null}"
注意事项
com.epages.restdocs-api-spec0.18.2版本对multipart/form-data的支持依赖requestParts,formParameters或requestSchema主要用于application/x-www-form-urlencoded类型的表单请求。- 若需兼容OpenAPI 2,只需将Gradle中的
openapi3配置改为openapi即可,上述requestParts配置同样适用。
内容的提问来源于stack exchange,提问作者김도연
相关产品推荐
相关产品推荐

