此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10! |
多部分内容
如 Multipart Data 中所述,ServerWebExchange
提供对 Multipart 的访问
内容。在控制器中处理文件上传表单(例如,从浏览器)的最佳方式
是通过与命令对象的数据绑定,
如以下示例所示:
-
Java
-
Kotlin
class MyForm {
private String name;
private FilePart file;
// ...
}
@Controller
public class FileUploadController {
@PostMapping("/form")
public String handleFormUpload(MyForm form, BindingResult errors) {
// ...
}
}
class MyForm(
val name: String,
val file: FilePart)
@Controller
class FileUploadController {
@PostMapping("/form")
fun handleFormUpload(form: MyForm, errors: BindingResult): String {
// ...
}
}
您还可以在 RESTful 服务中提交来自非浏览器客户端的多部分请求 场景。以下示例将文件与 JSON 一起使用:
POST /someUrl Content-Type: multipart/mixed --edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp Content-Disposition: form-data; name="meta-data" Content-Type: application/json; charset=UTF-8 Content-Transfer-Encoding: 8bit { "name": "value" } --edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp Content-Disposition: form-data; name="file-data"; filename="file.properties" Content-Type: text/xml Content-Transfer-Encoding: 8bit ... File Data ...
您可以使用@RequestPart
,如以下示例所示:
-
Java
-
Kotlin
@PostMapping("/")
public String handle(@RequestPart("meta-data") Part metadata, (1)
@RequestPart("file-data") FilePart file) { (2)
// ...
}
1 | 用@RequestPart 以获取元数据。 |
2 | 用@RequestPart 以获取文件。 |
@PostMapping("/")
fun handle(@RequestPart("meta-data") Part metadata, (1)
@RequestPart("file-data") FilePart file): String { (2)
// ...
}
1 | 用@RequestPart 以获取元数据。 |
2 | 用@RequestPart 以获取文件。 |
反序列化原始部分内容(例如,反序列化为 JSON - 类似于@RequestBody
),
您可以声明一个具体的目标Object
而不是Part
,如以下示例所示:
-
Java
-
Kotlin
@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata) { (1)
// ...
}
1 | 用@RequestPart 以获取元数据。 |
@PostMapping("/")
fun handle(@RequestPart("meta-data") metadata: MetaData): String { (1)
// ...
}
1 | 用@RequestPart 以获取元数据。 |
您可以使用@RequestPart
结合使用jakarta.validation.Valid
或 Spring 的@Validated
注释,这会导致应用标准 Bean 验证。验证
错误导致WebExchangeBindException
这会导致 400 (BAD_REQUEST) 的响应。
异常包含一个BindingResult
带有错误详细信息,也可以处理
在控制器方法中,使用异步包装器声明参数,然后使用
错误相关运算符:
-
Java
-
Kotlin
@PostMapping("/")
public String handle(@Valid @RequestPart("meta-data") Mono<MetaData> metadata) {
// use one of the onError* operators...
}
@PostMapping("/")
fun handle(@Valid @RequestPart("meta-data") metadata: MetaData): String {
// ...
}
如果方法验证适用,因为其他参数具有@Constraint
附注
然后HandlerMethodValidationException
而是被提高。请参阅验证部分。
要将所有多部分数据作为MultiValueMap
,您可以使用@RequestBody
,
如以下示例所示:
-
Java
-
Kotlin
@PostMapping("/")
public String handle(@RequestBody Mono<MultiValueMap<String, Part>> parts) { (1)
// ...
}
1 | 用@RequestBody . |
@PostMapping("/")
fun handle(@RequestBody parts: MultiValueMap<String, Part>): String { (1)
// ...
}
1 | 用@RequestBody . |
PartEvent
要以流式方式按顺序访问多部分数据,您可以使用@RequestBody
跟Flux<PartEvent>
(或Flow<PartEvent>
在 Kotlin 中)。
多部分 HTTP 消息中的每个部分将生成
至少一个PartEvent
包含标头和包含部件内容的缓冲区。
-
表单字段将生成单个
FormPartEvent
,包含字段的值。 -
文件上传将生成一个或多个
FilePartEvent
对象,包含所用文件名 上传时。如果文件足够大,可以拆分到多个缓冲区,则第一个FilePartEvent
随后将发生后续事件。
例如:
-
Java
-
Kotlin
@PostMapping("/")
public void handle(@RequestBody Flux<PartEvent> allPartsEvents) { (1)
allPartsEvents.windowUntil(PartEvent::isLast) (2)
.concatMap(p -> p.switchOnFirst((signal, partEvents) -> { (3)
if (signal.hasValue()) {
PartEvent event = signal.get();
if (event instanceof FormPartEvent formEvent) { (4)
String value = formEvent.value();
// handle form field
}
else if (event instanceof FilePartEvent fileEvent) { (5)
String filename = fileEvent.filename();
Flux<DataBuffer> contents = partEvents.map(PartEvent::content); (6)
// handle file upload
}
else {
return Mono.error(new RuntimeException("Unexpected event: " + event));
}
}
else {
return partEvents; // either complete or error signal
}
}));
}
1 | 用@RequestBody . |
2 | 决赛PartEvent 对于特定零件将具有isLast() 设置为true ,并且可以是
后跟属于后续部分的其他事件。
这使得isLast 适合作为谓词的属性Flux::windowUntil 运算符,设置为
将所有部件中的事件拆分为每个部件属于单个部件的窗口。 |
3 | 这Flux::switchOnFirst 运算符允许您查看您是在处理表单字段还是
文件上传。 |
4 | 处理表单字段。 |
5 | 处理文件上传。 |
6 | 正文内容必须完全消耗、中继或释放,以避免内存泄漏。 |
@PostMapping("/")
fun handle(@RequestBody allPartsEvents: Flux<PartEvent>) = { (1)
allPartsEvents.windowUntil(PartEvent::isLast) (2)
.concatMap {
it.switchOnFirst { signal, partEvents -> (3)
if (signal.hasValue()) {
val event = signal.get()
if (event is FormPartEvent) { (4)
val value: String = event.value();
// handle form field
} else if (event is FilePartEvent) { (5)
val filename: String = event.filename();
val contents: Flux<DataBuffer> = partEvents.map(PartEvent::content); (6)
// handle file upload
} else {
return Mono.error(RuntimeException("Unexpected event: " + event));
}
} else {
return partEvents; // either complete or error signal
}
}
}
}
1 | 用@RequestBody . |
2 | 决赛PartEvent 对于特定零件将具有isLast() 设置为true ,并且可以是
后跟属于后续部分的其他事件。
这使得isLast 适合作为谓词的属性Flux::windowUntil 运算符,设置为
将所有部件中的事件拆分为每个部件属于单个部件的窗口。 |
3 | 这Flux::switchOnFirst 运算符允许您查看您是在处理表单字段还是
文件上传。 |
4 | 处理表单字段。 |
5 | 处理文件上传。 |
6 | 正文内容必须完全消耗、中继或释放,以避免内存泄漏。 |
接收到的部件事件也可以使用WebClient
.
请参阅多部分数据。