// inside @RestController annotated class
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> upload(@RequestParam Map<String, MultipartFile> upfiles) throws Exception {
return ResponseEntity.ok(new Object());
}
在另一个微服务中,我创建了一个假客户端来触发上述端点:
// inside @FeginClient annotated class
@PostMapping(
value = "/upload",
produces = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.MULTIPART_FORM_DATA_VALUE
)
ResponseEntity<UploadResponse> uploadDocumentToSign(@Valid @RequestParam Map<String, MultipartFile> upfiles);
上述调用失败并显示消息“无法解析多部分 servlet 请求”
所以我从邮递员那里尝试了,从邮递员那里工作的唯一方法如下:
如您所见,地图作为表单数据放置在主体上,而不是方法定义所暗示的查询参数所以我尝试了与 feign 客户端相同的方法:
//inside @FeginClient annotated class
@PostMapping(
value = "/upload",
produces = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
ResponseEntity<UploadResponse> uploadDocumentToSign(@Valid @RequestBody Map<String,
MultipartFile> upfiles);
但这并不起作用,请求已到达服务器,但 Controller 类中的 Map 为空,即使在创建了 SpringFormEncoder
类型的编码器 Bean 之后也是如此。 似乎
SpringFormEncoder
不知道如何处理具有 Map类型主体的多部分/表单数据请求,所以我必须创建自己的
SpringFormEncoder
:
public class DocumentUploadSpringFormEncoder extends SpringFormEncoder {
public DocumentUploadSpringFormEncoder() {
}
public DocumentUploadSpringFormEncoder(Encoder delegate) {
super(delegate);
}
@Override
public void encode (Object object, Type bodyType, RequestTemplate template) throws EncodeException {
if (bodyType.equals(MultipartFile[].class)) {
val files = (MultipartFile[]) object;
val data = new HashMap<String, Object>(files.length, 1.F);
for (val file : files) {
data.put(file.getName(), file);
}
super.encode(data, MAP_STRING_WILDCARD, template);
} else if (bodyType.equals(MultipartFile.class)) {
val file = (MultipartFile) object;
val data = singletonMap(file.getName(), object);
super.encode(data, MAP_STRING_WILDCARD, template);
} else if (isMultipartFileCollection(object)) {
val iterable = (Iterable<?>) object;
val data = new HashMap<String, Object>();
for (val item : iterable) {
val file = (MultipartFile) item;
data.put(file.getName(), file);
}
super.encode(data, MAP_STRING_WILDCARD, template);
} else if (isMultipartFileMap(object)) {
val map = (Map<?, ?>) object;
val entrySet = map.entrySet();
val data = new HashMap<String, Object>();
for (Map.Entry entry : entrySet) {
val file = (MultipartFile) entry.getValue();
val key = (String) entry.getKey();
data.put(key, file);
}
super.encode(data, MAP_STRING_WILDCARD, template);
}
else {
super.encode(object, bodyType, template);
}
}
private boolean isMultipartFileCollection (Object object) {
if (!(object instanceof Iterable<?> iterable)) {
return false;
}
val iterator = iterable.iterator();
return iterator.hasNext() && iterator.next() instanceof MultipartFile;
}
private boolean isMultipartFileMap (Object object) {
if (!(object instanceof Map<?, ?> map)) {
return false;
}
val entrySet = map.entrySet();
for (Map.Entry entry : entrySet) {
if (!(entry.getValue() instanceof MultipartFile)) {
return false;
}
}
return true;
}
}
直到现在一切才正常。
我的问题是:
@RequestParam Map<String, MultipartFile> upfiles
,当你实际上需要将其放入正文中时为什么要@RequestParam?
Why @RequestPart?
For complex mapping eg:images, text. We can use RequestPart to map the value.
Example:
// inside @RestController annotated class
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> upload(@Valid @RequestPart Map<String, MultipartFile> upfiles) throws Exception {
return ResponseEntity.ok(new Object());
}
//inside @FeginClient annotated class
@PostMapping(
value = "/upload",
produces = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
ResponseEntity<UploadResponse> uploadDocumentToSign(@Valid @RequestPart Map<String,
MultipartFile> upfiles);