具有不同内容类型的WebFlux功能路由器定义中的@RequestPart行为镜像 [英] Mirror @RequestPart behavior in WebFlux functional router definitions with different content types
问题描述
我们正在开发Spring Boot服务,以将数据上传到不同的后端数据库.想法是,在一个multipart/form-data
请求中,用户将发送一个模型"消息给用户. (基本上是文件)和"modelMetadata" (这是JSON,它在我们的代码中定义了一个同名对象).
We're developing a Spring Boot service to upload data to different back end databases. The idea is that, in one multipart/form-data
request a user will send a "model" (basically a file) and "modelMetadata" (which is JSON that defines an object of the same name in our code).
当用户将"modelMetadata"发送给用户时,我们可以使用WebFlux注释的控制器语法进行以下操作.内容形式为"application/json"的多部分形式:
We got the below to work in the WebFlux annotated controller syntax, when the user sends the "modelMetadata" in the multipart form with the content-type of "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的功能路由器定义中弄清楚如何做同样的事情.以下是我们拥有的相关代码段:
But we can't seem to figure out how to do the same thing in Webflux's functional router definition. Below are the relevant code snippets we have:
@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>
}
从我们已阅读的所有内容中,我们应该能够使用路由器功能语法复制在注释控制器语法中发现的相同功能,但是似乎没有充分记录此特定方面.我们的目标是继续使用新的功能路由器语法,因为这是我们正在开发的新应用程序,并且有一些很好的前瞻性功能/优点,如
From everything we've read, we should be able to replicate the same functionality found in the annotation controller syntax with the router functional syntax, but this particular aspect doesn't seem to be well documented. Our goal was to move over to use the new functional router syntax since this is a new application we're developing and there are some nice forward thinking features/benefits as described here.
- 在地球尽头搜寻相关示例
- Googling to the ends of the Earth for a relevant example
- this is a similar question, but hasn't gained any traction and doesn't relate to our need to create an object from one piece of the multipart request data
- this may be close to what we need for uploading the file component of our multipart request data, but doesn't handle the object creation from JSON
the content of the part is passed through an {@link HttpMessageConverter} taking into consideration the 'Content-Type' header of the request part.
任何人和所有帮助将不胜感激!即使只是一些链接,我们也可以更好地了解Part/FilePart类型,并且在多部分请求中发挥作用也将有所帮助!
Any and all help would be appreciated! Even just some links for us to better understand Part/FilePart types and there role in multipart requests would be helpful!
推荐答案
我能够使用自动连接的
ObjectMapper
提出解决此问题的方法.从下面的解决方案中,我可以将modelMetadata
和modelPart
转换为Mono
来镜像@RequestPart
返回类型,但这似乎很荒谬.I was able to come up with a solution to this issue using an autowired
ObjectMapper
. From the below solution I could turn themodelMetadata
andmodelPart
intoMono
s to mirror the@RequestPart
return types, but that seems ridiculous.我也可以通过创建
MappingJackson2HttpMessageConverter
并将metadataDataBuffer
转换为MappingJacksonInputMessage
来解决此问题,但是这种解决方案似乎更符合我们的需求.I was also able to solve this by creating a
MappingJackson2HttpMessageConverter
and turning themetadataDataBuffer
into aMappingJacksonInputMessage
, but this solution seemed better for our needs.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
批注注释中提到的那样优雅.因此,我现在将其作为解决方案,但是如果有人有更好的解决方案,请告诉我们,我会接受!Although this solution works, I feel like it's not as elegant as the one alluded to in the
@RequestPart
annotation comments. Thus I will accept this as the solution for now, but if someone has a better solution please let us know and I will accept it!这篇关于具有不同内容类型的WebFlux功能路由器定义中的@RequestPart行为镜像的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
- Googling to the ends of the Earth for a relevant example