Если я отправляю файл 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, так и составной файл.
Поэтому я не могу поместить всю свою проверку в один и тот же класс.
У меня нет тестов в моей текущей производственной среде: / Я использую @RestController, так как это Restful API, и пока он работает. Я просто не получаю правильной обработки исключений, я посмотрю!




Обновлять:
.. Я хотел бы иметь возможность применять аннотации проверки к моим свойствам 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.
нет, в том-то и дело: или у вас есть «точная» проверка типа контента, или у вас есть (неявный! тип контента, но все же очень гибкий) и есть «проверка json» .. я попробую это;)
Хорошо, тогда я не уверен, что понимаю. Я должен изучить это. Само по себе неважно, что это неявно, но как я могу проверить с помощью аннотаций, что свойство X моего json с @NotNull, @Min(3), @Max(7) ?
Спасибо !!! Приятно видеть таких людей, как вы, которые не жалеют времени, чтобы помочь другим! Однако я вижу, что это обязательно @Validated в верхней части контроллера. В результате моя последняя проблема заключается в том, что выдается исключение ConstraintViolationException, а не MethodArgumentNotValidException или BindException, которые я уже обрабатываю с помощью @ControllerAdvice для форматирования ошибок для моего API Rest. Есть ли простой способ справиться с этим, не переписывая @ExceptionHandler для этого конкретного случая?
.. обработка исключений сложна, особенно. В среде "mock mvc"... именно по этой причине :) ... См. здесь(e.a.)..
Когда мы опустим наш обработчик , тесты будут терпеть неудачу... (с точными симптомами:);(;( Мне очень повезло, что он заработал -2 года назад !:)
Точно, мне нужен обработчик, потому что я хотел бы, чтобы ошибки проверки были отформатированы в json с соответствующим полем, а не выбрасывались ConstraintViolationException. Я уже делаю это с MethodArgumentNotValidException. Тогда я посмотрю, что я могу сделать, спасибо! Но кажется более сложным получить поле с ошибкой из этого исключения. А пока я принимаю ваш ответ, поскольку он довольно хорошо отвечает на основную проблему.
Пожалуйста, также протестируйте его в «реальном контейнере» (а не только в фиктивной среде) .. поэкспериментируйте!) В «полной» (сервлет) среде он будет вести себя иначе/лучше! Чтобы протестировать «полный» контекст: замените все «mockMvc» на «(test)restTemplate/webclient/любой другой http-клиент (tech)», сохранив
@SpringBootTest... см. spring.io/guides/gs/testing-web + ссылки ниже