未通过all args构造函数设置多部分/表单数据mutlipartfile

mitkmikd  于 2021-07-05  发布在  Java
关注(0)|答案(1)|浏览(293)

背景

这个问题是在我执行任务时出现的 POST multipart/form-data 文件上载的终结点,其中包含一些元信息。我不是春靴生态系统的Maven;这个问题很可能是通过一个简单的修复方法解决的,而我只是缺少了一个合适的搜索词。

问题陈述

为了实现一个带有附加元信息的文件上传端点,我编写了以下代码 @RestController :

  1. @RestController
  2. @RequestMapping(Resource.ROOT)
  3. @AllArgsConstructor(onConstructor = @__({@Inject}))
  4. public class Resource {
  5. public static final String ROOT = "/test";
  6. private final Logger logger;
  7. @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA, produces = MediaType.APPLICATION_JSON)
  8. public ResponseEntity<Void> test(@Valid final Request request) {
  9. logger.info("request = {}", request);
  10. return ResponseEntity.ok().build();
  11. }
  12. }

Request 指定为:

  1. @Value
  2. @AllArgsConstructor
  3. public class Request {
  4. @NotNull
  5. String name;
  6. @NotNull
  7. MultipartFile file;
  8. }

还有一个快乐小径测试:

  1. @SpringBootTest
  2. @AutoConfigureMockMvc
  3. class TestCase {
  4. @Autowired
  5. private MockMvc mockMvc;
  6. @Test
  7. void shouldReturnOk() throws Exception {
  8. // GIVEN
  9. final byte[] content = Files.readAllBytes(Path.of(".", "src/test/resources/PenPen.png"));
  10. final String name = "name";
  11. // WHEN
  12. // @formatter:off
  13. mockMvc.perform(MockMvcRequestBuilders
  14. .multipart(Resource.ROOT)
  15. .file("file", content)
  16. .param("name", name))
  17. // THEN
  18. .andExpect(status().isOk());
  19. // @formatter:on
  20. }
  21. }

完整的mre可以在bitbucket、branch上找到 problem-with-immutable-request .
运行测试时( ./mvnw test) ,它失败,端点返回 400 BAD REQUEST 而不是 200 OK . 读取日志会发现请求参数 filenull :

  1. ...
  2. Content type = text/plain;charset=UTF-8
  3. Body = file: must not be null.
  4. ...

我部分理解为什么是这样 null . 有了这部分知识,我就可以通过做这个领域来规避这个问题 fileRequest 可变:

  1. @ToString
  2. @Getter
  3. @AllArgsConstructor
  4. public class Request {
  5. @NotNull
  6. private final String name;
  7. @Setter
  8. @NotNull
  9. private MultipartFile file;
  10. }

“修复”问题的代码可以在bitbucket、branch上找到 problem-solved-by-making-field-mutable .
然而,这使得 Request 易变的,我想阻止。为了进一步研究,我展开了 Request 并添加了一些日志记录:

  1. public class Request {
  2. private static final Logger LOGGER = LoggerFactory.getLogger(Request.class);
  3. @NotNull
  4. private final String name;
  5. @NotNull
  6. private MultipartFile file;
  7. public Request(final String name, final MultipartFile file) {
  8. this.name = name;
  9. this.setFile(file);
  10. }
  11. public @NotNull String getName() {
  12. return this.name;
  13. }
  14. public @NotNull MultipartFile getFile() {
  15. return this.file;
  16. }
  17. public String toString() {
  18. return "Request(name=" + this.getName() + ", file=" + this.getFile() + ")";
  19. }
  20. public void setFile(final MultipartFile file) {
  21. LOGGER.info("file = {}", file);
  22. this.file = file;
  23. }
  24. }

展开版本的代码可以在bitbucket、branch上找到 lombok-unrolled-for-debugging .
当查看现在成功的测试的日志语句时,我们可以看到 Request::setFile 调用两次:

  1. 2020-09-05 09:42:31.049 INFO 11012 --- [ main] d.turing85.springboot.multipart.Request : file = null
  2. 2020-09-05 09:42:31.056 INFO 11012 --- [ main] d.turing85.springboot.multipart.Request : file = org.springframework.mock.web

第一个调用来自构造函数调用。我想,第二个调用来自spring的表单参数Map机制。
我知道有可能在端点上单独定义表单参数并构造 Request 方法中的示例:

  1. public class Resource {
  2. ...
  3. @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA, produces = MediaType.APPLICATION_JSON)
  4. public ResponseEntity<Void> test(
  5. @RequestPart(name = "name") final String name,
  6. @RequestPart(name = "file") final MultipartFile file) {
  7. final Request request = new Request(name, file);
  8. logger.info("request = {}", request);
  9. return ResponseEntity.ok().build();
  10. }
  11. }

然而,这将导致其他问题。例如,我们必须为添加一个额外的异常Map器 MissingServletRequestPartException 并将返回的http响应与的现有响应对齐 BindException . 如果可能的话,我想避免这种情况。
对这个主题的搜索出现了springbootcontroller-upload multipart和json-to-dto。然而,这个解决方案并不适合我,因为我不使用mvc(我认为)。

问题

有没有可能 Request 不变的,这样Spring就可以通过 MultipartFile 而不是事后通过setter设置它?编写自定义Map器/转换器是可以接受的,但我没有发现为特定端点或特定类型编写Map器的可能性。

7ajki6be

7ajki6be1#

  1. @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
  2. public ResponseEntity<Void> test(@Valid @ModelAttribute final RequestDto request) {
  3. return ResponseEntity.ok().build();
  4. }

它仍在使用restapi调用。但我真的没有得到你的不变性的担心。
如果您定义setter多部分数据,您可以使用modeldattribute。

  1. @SpringBootTest
  2. @AutoConfigureMockMvc
  3. class FileUploadControllerIT {
  4. @Autowired
  5. private MockMvc mockMvc;
  6. @Test
  7. void shouldReturnOk() throws Exception {
  8. // GIVEN
  9. final byte[] content = Files.readAllBytes(Paths.get(Thread.currentThread().getContextClassLoader().getResource("text.txt").toURI()));
  10. final String name = "name";
  11. // WHEN
  12. // @formatter:off
  13. mockMvc.perform(MockMvcRequestBuilders
  14. .multipart("/context/api/v1")
  15. .file("multipartFile", content)
  16. .param("name", name))
  17. // THEN
  18. .andExpect(status().isOk());
  19. // @formatter:on
  20. }
  21. }

上面的代码使用modelattribute。
而且你给出的绝对路径,我猜是错的。你可以用classloader获取文件。

展开查看全部

相关问题