Spring: проверка с помощью @RequestPart не работает

Если я отправляю файл PNG, имя файла возвращается с кодом HTTP 200. В то время как только файл PDF должен быть принят:

@PutMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<String> send(
    @PathVariable Integer id,
    @Valid @FileContentType(contentTypes = {
      MediaType.APPLICATION_PDF_VALUE
    }) @RequestPart("custom_file") MultipartFile file
) {
    return new ResponseEntity<>(file.getOriginalFilename(), HttpStatus.OK);
}

Обратите внимание, что мой валидатор работает, когда я использую его в @RequestBody...

Я попытался зарегистрировать свой валидатор, чтобы увидеть, срабатывает он или нет, но я даже не захожу.

РЕДАКТИРОВАТЬ 1 (частичное решение):

@Valid работает только для сложных объектов. Это не работает с @RequestParam/Part напрямую, потому что нет привязки. Самый простой способ — создать класс, объединяющий составной файл и содержащий логику проверки.

Однако это не относится к моему случаю, потому что на самом деле я хочу, чтобы он работал со смешанным контентом. Я отправляю как тело json, так и составной файл.

Поэтому я не могу поместить всю свою проверку в один и тот же класс.

Пожалуйста, также протестируйте его в «реальном контейнере» (а не только в фиктивной среде) .. поэкспериментируйте!) В «полной» (сервлет) среде он будет вести себя иначе/лучше! Чтобы протестировать «полный» контекст: замените все «mockMvc» на «(test)restTemplate/webclient/любой другой http-клиент (tech)», сохранив @SpringBootTest... см. spring.io/guides/gs/testing-web + ссылки ниже

xerx593 28.04.2023 14:01

У меня нет тестов в моей текущей производственной среде: / Я использую @RestController, так как это Restful API, и пока он работает. Я просто не получаю правильной обработки исключений, я посмотрю!

Jedupont 28.04.2023 14:13
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
2
98
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Обновлять:

.. Я хотел бы иметь возможность применять аннотации проверки к моим свойствам json

Да, мы тоже можем это сделать, но не можем использовать аннотацию «ValidMediaType» на SomeDto (как корень):

// ...
@PutMapping(value = "/pdf/json/v2", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
  public ResponseEntity<String> pdf2Controller(
      @Valid @RequestPart SomeDto someJson,
      @Valid @ValidMediaType(MediaType.APPLICATION_PDF_VALUE) @RequestPart MultipartFile someFile) throws IOException {
    System.err.println(someJson);
    return new ResponseEntity<>(someFile.getOriginalFilename(), HttpStatus.OK);
  }
}

@lombok.Value(staticConstructor = "of")
class SomeDto {
  @NotNull
  String foo;
  @Size(min = 3, max = 6)
  String bar;
}

И тесты...:

@Test
void testPdfJson2Valid() throws Exception {
  final MockPart jsonPart = new MockPart("someJson", "test.json",
      testValidJson.getInputStream().readAllBytes());
  // we need this! (so implicitely validate, but other error code)
  jsonPart.getHeaders().setContentType(MediaType.APPLICATION_JSON);
  mockMvc.perform(putMultipart("/validated/pdf/json/v2")
      .file(mockPdf)
      .part(jsonPart)) // !
      .andExpectAll(status().isOk(), // !!
          content().string("aha.pdf"));
}

@Test
void testPdfJson2ValidAlt() throws Exception {
  mockMvc.perform(
      putMultipart("/validated/pdf/json/v2")
          .file(mockPdf)
          // also
          .file(validJsonFile()))
      .andExpectAll(status().isOk(), // ..works !!
          content().string("aha.pdf"));
}

@Test
void testPdfJson2unsupportedMediaType() throws Exception {
  MockPart jsonPart = new MockPart("someJson", "test.json", testValidJson.getInputStream().readAllBytes());
  mockMvc.perform(
      putMultipart("/validated/pdf/json/v2")
          .file(mockPdf)
          .part(jsonPart)) // no/wrong content type
      .andExpect(status().isUnsupportedMediaType()); // !
}

@Test
void testPdfJson2Invalid() throws Exception {
  MockPart jsonPart = new MockPart("someJson", "test.json", testInvalidJson.getInputStream().readAllBytes());
  jsonPart.getHeaders().setContentType(MediaType.APPLICATION_JSON);
  mockMvc.perform(
      putMultipart("/validated/pdf/json/v2")
          .file(mockPdf)
          .part(jsonPart))
      .andExpect(status().isBadRequest()); // !
}

все сюда

Оригинал:


Я обновил свой старый репозиторий вашим новым контроллером:

@PutMapping(value = "/pdf/{id}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<String> pdfController(
    @PathVariable Integer id,
    @Valid @ValidMediaType(MediaType.APPLICATION_PDF_VALUE) @RequestPart("custom_file") MultipartFile file) {
  // just a test/demo:
  return new ResponseEntity<>(file.getOriginalFilename(), HttpStatus.OK);
}

..и тесты:

@Test
void testPdfInvalid() throws Exception {
  mockMvc.perform(
      putMultipart("/validated/pdf/1")
          .file(new MockMultipartFile(
              "custom_file",
              "ohoh.jpg",
              MediaType.IMAGE_JPEG_VALUE, // ??
              (byte[]) null)))
      .andExpect(status().isBadRequest()) // !!
      .andExpect(content().string(endsWith("application/pdf.\"}")));
}

@Test
void testPdfValid() throws Exception {
  mockMvc.perform(
      putMultipart("/validated/pdf/1")
          .file(new MockMultipartFile(
              "custom_file",
              "aha.pdf",
              MediaType.APPLICATION_PDF_VALUE, // !
              (byte[]) null)))
      .andExpectAll(status().isOk(), // !!
          content().string("aha.pdf")); /// !!!
}

... работает как шарм.


Вот как я могу справиться со сценарием составных файлов + частей json:

Обновленный контроллер:

// 1.
import javax.servlet.http.Part;
// - it has built-in handling (as method argument by spring-web(flux))
// - it has "content-type" attribute (to validate)
// - and it has input stream (to read json) 
// ...

// normally we would get "json objects" in controller, now due to validation, we have to read it from the Part
@Autowired ObjectMapper objectMapper;

@PutMapping(value = "/pdf/json", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<String> pdf1Controller(
    @Valid @ValidMediaType(MediaType.APPLICATION_JSON_VALUE) @RequestPart Part someJson,
    @Valid @ValidMediaType(MediaType.APPLICATION_PDF_VALUE) @RequestPart MultipartFile someFile) throws IOException {
  System.err.println(
    objectMapper.readValue(someJson.getInputStream(), SomeDto.class)
  );
  return new ResponseEntity<>(someFile.getOriginalFilename(), HttpStatus.OK);
}

Нам нужно расширить/дополнительный валидатор (который способен на Part и нашу «пользовательскую аннотацию»):

class MultiPartPartValidator implements ConstraintValidator<ValidMediaType, Part> { // ... analogous to existing

Нам нужно упомянуть/включить/связать это в нашей аннотации:

@Constraint(validatedBy = {MultiPartPartValidator.class // , ... 

Тесты (самая сложная часть:) могут выглядеть так:

@Test
void testPdfJsonValid() throws Exception {
  MockPart jsonPart = new MockPart("someJson", "test.json", testJson.getInputStream().readAllBytes());
  jsonPart.getHeaders().setContentType(MediaType.APPLICATION_JSON);
  mockMvc.perform(
      putMultipart("/validated/pdf/json")
          .file(mockPdf)
          .part(jsonPart)) // !
      .andExpectAll(status().isOk(), // !!
          content().string("aha.pdf"));
}

@Test
void testPdfJsonInvalid() throws Exception {
  mockMvc.perform(
      putMultipart("/validated/pdf/json")
          .file(mockPdf)
          .part(new MockPart("someJson", "test.json", testJson.getInputStream().readAllBytes()))) // ??
      .andExpect(status().isBadRequest()); // !
}

Также хорошо смотрится в почтальоне:

Спасибо за ваш ответ, но проблема в том, что таким образом будет ли objectMapper.readValue(...) контролировать аннотации проверки для каждого свойства в моем DTO? Я хотел бы иметь возможность применять аннотации проверки к моим свойствам json.

Jedupont 28.04.2023 09:59

нет, в том-то и дело: или у вас есть «точная» проверка типа контента, или у вас есть (неявный! тип контента, но все же очень гибкий) и есть «проверка json» .. я попробую это;)

xerx593 28.04.2023 10:01

Хорошо, тогда я не уверен, что понимаю. Я должен изучить это. Само по себе неважно, что это неявно, но как я могу проверить с помощью аннотаций, что свойство X моего json с @NotNull, @Min(3), @Max(7) ?

Jedupont 28.04.2023 10:30

Спасибо !!! Приятно видеть таких людей, как вы, которые не жалеют времени, чтобы помочь другим! Однако я вижу, что это обязательно @Validated в верхней части контроллера. В результате моя последняя проблема заключается в том, что выдается исключение ConstraintViolationException, а не MethodArgumentNotValidException или BindException, которые я уже обрабатываю с помощью @ControllerAdvice для форматирования ошибок для моего API Rest. Есть ли простой способ справиться с этим, не переписывая @ExceptionHandler для этого конкретного случая?

Jedupont 28.04.2023 12:59

.. обработка исключений сложна, особенно. В среде "mock mvc"... именно по этой причине :) ... См. здесь(e.a.)..

xerx593 28.04.2023 13:44

Когда мы опустим наш обработчик , тесты будут терпеть неудачу... (с точными симптомами:);(;( Мне очень повезло, что он заработал -2 года назад !:)

xerx593 28.04.2023 13:47

Точно, мне нужен обработчик, потому что я хотел бы, чтобы ошибки проверки были отформатированы в json с соответствующим полем, а не выбрасывались ConstraintViolationException. Я уже делаю это с MethodArgumentNotValidException. Тогда я посмотрю, что я могу сделать, спасибо! Но кажется более сложным получить поле с ошибкой из этого исключения. А пока я принимаю ваш ответ, поскольку он довольно хорошо отвечает на основную проблему.

Jedupont 28.04.2023 13:52

Другие вопросы по теме