Зеркальное отражение поведения @RequestPart в определениях функционального маршрутизатора WebFlux с разными типами контента - PullRequest
0 голосов
/ 08 июля 2020

Проблема

Мы разрабатываем службу Spring Boot для загрузки данных в различные серверные базы данных. Идея состоит в том, что в одном multipart/form-data запросе пользователь отправит «модель» (в основном файл) и «modelMetadata» (это JSON, определяющее объект с таким же именем в нашем коде).

У нас есть следующее, чтобы работать в синтаксисе аннотированного контроллера WebFlux, когда пользователь отправляет «modelMetadata» в составной форме с типом содержимого «application / json»:

    @PostMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
    fun saveModel(@RequestPart("modelMetadata") monoModelMetadata: Mono<ModelMetadata>,
                  @RequestPart("model") monoModel: Mono<FilePart>,
                  @RequestHeader headers: HttpHeaders) : Mono<ResponseEntity<ModelMetadata>> {
        return modelService.saveModel(monoModelMetadata, monoModel, headers)
    }

Но мы, кажется, не можем понять, как сделать то же самое в определении функционального маршрутизатора Webflux. Ниже приведены соответствующие фрагменты кода, которые у нас есть:

    @Bean
    fun modelRouter() = router {
        accept(MediaType.MULTIPART_FORM_DATA).nest {
            POST(ROOT, handler::saveModel)
        }
    }


    fun saveModel(r: ServerRequest): Mono<ServerResponse> {
        val headers = r.headers().asHttpHeaders()
        val monoModelPart = r.multipartData().map { multiValueMap ->
            it["model"] // What do we do with this List<Part!> to get a Mono<FilePart>
            it["modelMetadata"] // What do we do with this List<Part!> to get a Mono<ModelMetadata>
        }

Из всего, что мы читали, мы должны иметь возможность воспроизвести те же функции, что и в синтаксисе контроллера аннотаций, с функциональным синтаксисом маршрутизатора, но этот конкретный аспект не кажется хорошо документированным. Наша цель состояла в том, чтобы перейти к использованию нового функционального синтаксиса маршрутизатора, поскольку это новое приложение, которое мы разрабатываем, и есть несколько хороших перспективных функций / преимуществ, описанных здесь .

Что мы пробовали

  • Погуглить до края Земли для соответствующего примера
    • это - аналогичный вопрос, но не получил никакой поддержки и не имеет отношения к нашей необходимости создать объект из одного фрагмента данных многостраничного запроса
    • это может быть близко к тому, что нам нужно для загрузки файлового компонента наших данных многостраничного запроса, но не обрабатывает создание объекта из JSON
  • Пытался взглянуть на код аннотации @RequestPart, чтобы увидеть, как все делается на этой стороне, есть хороший комментарий, который, кажется, намек на то, как они преобразуют части в объекты, но мы не смогли выяснить, где находится этот код или какой-либо соответствующий пример того, как использовать HttpMessageConverter на ``
the content of the part is passed through an {@link HttpMessageConverter} taking into consideration the 'Content-Type' header of the request part.

Любая помощь будет оценена! Было бы полезно даже несколько ссылок, чтобы мы лучше понимали типы Part / FilePart и их роль в составных запросах!

1 Ответ

0 голосов
/ 09 июля 2020

Мне удалось найти решение этой проблемы, используя автоподключение ObjectMapper. Из приведенного ниже решения я мог бы превратить modelMetadata и modelPart в Mono s, чтобы отразить возвращаемые типы @RequestPart, но это кажется смешным.

Я также смог решить эту проблему, создав MappingJackson2HttpMessageConverter и превращение metadataDataBuffer в MappingJacksonInputMessage, но это решение показалось лучше для наших нужд.

    fun saveModel(r: ServerRequest): Mono<ServerResponse> {
        val headers = r.headers().asHttpHeaders()
        return r.multipartData().flatMap {
            // We're only expecting one Part of each to come through...assuming we understand what these Parts are
            if (it.getOrDefault("modelMetadata", listOf()).size == 1 && it.getOrDefault("model", listOf()).size == 1) {
                val modelMetadataPart = it["modelMetadata"]!![0]
                val modelPart = it["model"]!![0] as FilePart
                modelMetadataPart
                        .content()
                        .map { metadataDataBuffer ->
                            // TODO: Only do this if the content is JSON?
                            objectMapper.readValue(metadataDataBuffer.asInputStream(), ModelMetadata::class.java)
                        }
                        .next() // We're only expecting one object to be serialized from the buffer
                        .flatMap { modelMetadata ->
                            // Function was updated to work without needing the Mono's of each type 
                            // since we're mapping here
                            modelService.saveModel(modelMetadata, modelPart, headers)
                        }
            }
            else {
                    // Send bad request response message
            }
        }

Хотя это решение работает, я чувствую, что оно не так элегантно, как упомянутое в комментариях к аннотации @RequestPart. Таким образом, я приму это как решение на данный момент, но если у кого-то есть лучшее решение, сообщите нам, и я приму его!

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