Spring: Использование RequestPart вместо RequestBody

Не могу на сервере принять запрос из клиента с телом Json Object & MultipartFile. Выдает следующие ошибки:

The request was rejected because no multipart boundary was found:

formData.append('idea', JSON.stringify(idea));
formData.append('addImages', file);
headers: {
  'Content-Type': 'multipart/mixed'
}

Required request part 'idea' is not present:

formData.append('idea', JSON.stringify(idea));
formData.append('addImages', file);
headers: {
  'Content-Type': 'multipart/mixed;boundary=gc0p4Jq0M2Yt08jU534c0p'
}

Content type 'application/octet-stream not supported:

formData.append('idea', JSON.stringify(idea));
formData.append('addImages', file);

Controller

@PostMapping("/idea/save")
public ResponseEntity<Idea> saveIdea(@RequestPart("idea") @Valid Idea idea,
                                     @RequestPart(required = false) List<MultipartFile> addImages,
                                     @RequestPart(required = false) List<MultipartFile> addFiles,
                                     Principal principal) {

    return new ResponseEntity<>(ideaService.add(idea, addImages, addFiles, principal.getName()), HttpStatus.CREATED);
}

Скорее всего нужно добавить спрингу какой-то конвертер и применить случай 1 или 2. Если это так, то дайте наводку на чертеж.


Ответы (2 шт):

Автор решения: Митяй

Решил с помощью реализации AbstractJackson2HttpMessageConverter

AbstractJackson2HttpMessageConverter

@Component
public class MultipartJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {

/**
 * Converter for support http request with header Content-Type: multipart/form-data
 */
public MultipartJackson2HttpMessageConverter(ObjectMapper objectMapper) {
    super(objectMapper, MediaType.APPLICATION_OCTET_STREAM);
}

@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
    return false;
}

@Override
public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
    return false;
}

@Override
protected boolean canWrite(MediaType mediaType) {
    return false;
}
}

Controller

@PostMapping("/idea/save")
public ResponseEntity<Idea> saveIdea(@RequestPart("idea") @Valid Idea idea,
                                     @RequestPart(required = false) List<MultipartFile> addImages,
                                     @RequestPart(required = false) List<MultipartFile> addFiles,
                                     Principal principal) {

    return new ResponseEntity<>(ideaService.add(idea, addImages, addFiles, principal.getName()), HttpStatus.CREATED);
}

Client

public put(idea: Idea, formData: FormData): Observable<Idea> {
  formData.append('addImages', this.addImages);
  formData.append('addImages', this.addFiles);
  formData.append('idea', JSON.stringify(idea));
  return this.httpClient.put<Idea>(`${this.apiBaseUrl}/idea/put`, formData);
}

Благодаря https://stackoverflow.com/questions/16230291/requestpart-with-mixed-multipart-request-spring-mvc-3-2/18503158

→ Ссылка
Автор решения: Victor Shulpov

Столкнулся с подобной проблемой. Отправлял в одном запросе файлы и JSON. Не знал, что для каждой строки из form-data можно указывать content-type отдельно. Когда установил content-type для моего JSON, спринг сам смог распарсить объект без явного указания, как ему это делать (мапперы, конвертеры).

Эндпоинт на java spring framework (boot)

@RestController
@RequestMapping("/api/v1/spot")

//...

@PostMapping
public ResponseEntity<?> createSpotForModeration(
@RequestPart("spot") NewSpotDTO dto,
@RequestPart("files") List<MultipartFile> files) {...}

Запрос через curl, для spot указываем явно content-type (type=application/json). Для картинок curl, насколько я понял, сам установит multipart/form-data + boundary

curl --location 'localhost:8080/api/v1/spot' \
--form 'files=@"/some-path/img1.jpg"' \
--form 'files=@"/some-path/img2.jpeg"' \
--form 'spot="{
    \"name\": \"Имя некоторой сущности\",
    \"description\": \"Описание некоторой сущности\"
}";type=application/json'

P.S. делая, запрос в postman, нужно ставить галочку и устанавливать content-type вручную для каждого form-data.

Надеюсь, мое дополнение кому-то поможет.

→ Ссылка