文件上传 spring 云伪装客户端
File upload spring cloud feign client
当使用 spring cloud netflix 的 feign 客户端从一个微服务向另一个微服务发出 post 请求时,我在 Postman 中收到以下错误:
{
"timestamp": 1506933777413,
"status": 500,
"error": "Internal Server Error",
"exception": "feign.codec.EncodeException",
"message": "Could not write JSON: No serializer found for class java.io.FileDescriptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS); nested exception is com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class java.io.FileDescriptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile[\"inputStream\"]->java.io.FileInputStream[\"fd\"])",
"path": "/attachments"
}
我的 eclipse 控制台显示以下异常:
com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class java.io.FileDescriptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile["inputStream"]->java.io.FileInputStream["fd"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:284) ~[jackson-databind-2.8.9.jar:2.8.9]
at com.fasterxml.jackson.databind.SerializerProvider.mappingException(SerializerProvider.java:1110) ~[jackson-databind-2.8.9.jar:2.8.9]
at com.fasterxml.jackson.databind.SerializerProvider.reportMappingProblem(SerializerProvider.java:1135) ~[jackson-databind-2.8.9.jar:2.8.9]
at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.failForEmpty(UnknownSerializer.java:69) ~[jackson-databind-2.8.9.jar:2.8.9]
at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.serialize(UnknownSerializer.java:32) ~[jackson-databind-2.8.9.jar:2.8.9]
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:704) ~[jackson-databind-2.8.9.jar:2.8.9]
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:689) ~[jackson-databind-2.8.9.jar:2.8.9]
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155) ~[jackson-databind-2.8.9.jar:2.8.9]
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:704) ~[jackson-databind-2.8.9.jar:2.8.9]
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:689) ~[jackson-databind-2.8.9.jar:2.8.9]
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155) ~[jackson-databind-2.8.9.jar:2.8.9]
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:292) ~[jackson-databind-2.8.9.jar:2.8.9]
at com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1429) ~[jackson-databind-2.8.9.jar:2.8.9]
at com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:951) ~[jackson-databind-2.8.9.jar:2.8.9]
更新 1
这是我的假界面:
@FeignClient(name="attachment-service", fallback=AttachmentHystrixFallback.class)
public interface AttachmentFeignClient {
@RequestMapping("upload")
void upload(@RequestPart(name="file") MultipartFile file, @RequestParam(name="attachableId") Long attachableId,
@RequestParam(name="className") String className, @RequestParam(name="appName") String appName);
这是主要的微服务控制器:
@RestController
public class AttachmentController implements Serializable {
/**
*
*/
private static final long serialVersionUID = -4431842080646836475L;
@Autowired
AttachmentService attachmentService;
@RequestMapping(value = "attachments", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public void upload(@RequestPart MultipartFile file, @RequestParam Long attachableId, @RequestParam String className, @RequestParam String appName) throws Exception {
attachmentService.uploadFile(file, attachableId, className, appName);
}
}
我肯定在这里缺少某种序列化器
任何建议,将不胜感激!
谢谢
在寻找解决方案几天后,我找到了这个。您应该开始为 spring 依赖项添加伪装形式:
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form-spring</artifactId>
<version>3.3.0</version>
</dependency
那么你的假客户需要这个 spring 表单编码器:
@FeignClient(
name="attachment-service",
configuration = {AttachmentFeignClient.MultipartSupportConfig.class}
fallback=AttachmentHystrixFallback.class)
public interface AttachmentFeignClient {
@RequestMapping(value= {"upload"}, consumes = {"multipart/form-data"})
void upload(
@RequestPart(name="file") MultipartFile file,
@RequestParam(name="attachableId") Long attachableId,
@RequestParam(name="className") String className,
@RequestParam(name="appName") String appName);
public class MultipartSupportConfig {
@Bean
@Primary
@Scope("prototype")
public Encoder feignFormEncoder() {
return new SpringFormEncoder();
}
}
}
希望对大家有所帮助。
更新 13/01/2020
配置 FeignClient 以使用 @RequestPart
处理 MultiPartFile
请注意,此方法仅适用于 one RequestPart 表示整个正文的情况。拥有多个 RequestPart 是 FeignClient 的另一个问题。
feignclient 的配置与@marting-choraine 所做的类似,但是通过在FeignConfigurationClass 中添加额外的编码器配置。
因此,对于我在下面提供的相同示例,您需要执行类似这样的操作,而不是实现自定义映射器
@Configuration
@EnableFeignClients(basePackages = "YourPackage")
public class FeignConfiguration {
@Bean
public Encoder feignFormEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
}
TL;DR
将您的 MultiPartFile 转换为 MultiValueMap。请参阅下面的示例
@martin-choraine 提到的答案是拥有 FeignClient 方法签名的正确和最佳答案,与您尝试调用的实际端点签名相同。但是,有一种方法不需要您定义 FormEncoder 或添加任何额外的依赖项,因为在某些应用程序中您不允许这样做(企业狗屎);您所需要的只是将您的 MultipartFile 转换为 MultiValueMap,它将完美地工作,因为标准编码器将能够对其进行序列化。
要调用的实际端点
@PostMapping(path = "/add", consumes = MULTIPART_FORM_DATA_VALUE, produces = APPLICATION_JSON_VALUE)
public MyResponseObject add(@RequestParam(name = "username") String username,
@RequestPart(name = "filetoupload") MultipartFile file) {
Do something
}
FeignClient 中的 POST 方法应该如下所示
@PostMapping(path = "/myApi/add", consumes = MULTIPART_FORM_DATA_VALUE,
produces = APPLICATION_JSON_VALUE)
public MyResponseObject addFile(@RequestParam(name = "username") String username,
@RequestPart(name = "filetoupload") MultiValueMap<String, Object> file);
然后当您调用 addFile 时,您应该像这样提供 MultiValueMap
public MyResponseObject addFileInAnotherEndPoint(String username, MultipartFile file) throws IOException {
MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>();
ByteArrayResource contentsAsResource = new ByteArrayResource(file.getBytes()) {
@Override
public String getFilename() {
return file.getOriginalFilename();
}
};
multiValueMap.add("filetoupload", contentsAsResource);
multiValueMap.add("fileType", file.getContentType());
return this.myFeignClient.addFile(username, multiValueMap);
}
我在 post 中添加了 consumes = MediaType.MULTIPART_FORM_DATA_VALUE,它对我有用。
这是我的假客户端方法,在前面我使用了 formdata
@PostMapping(path=Urls.UPLOAD_FILE_IN_LIBELLE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE )
public void uploadFileInLibelle(
@RequestParam String processus,
@RequestParam String level0Name,
@RequestParam String nomFichier,
@RequestParam String nomLibelle,
@RequestParam String anneeFolderName,
@RequestParam String semaineFolderName,
@RequestPart MultipartFile fichier);
这是我的 angular 前台
public uploadFiles(
nomFichier: string,
nomLibelle: string,
processus: string,
level0Name: string,
semaineFolderName: string,
anneeFolderName: string,
byte: File
): Observable<any> {
const formData = new FormData();
formData.set('processus', processus);
formData.set('level0Name', level0Name);
formData.set('nomLibelle', nomLibelle);
formData.set('anneeFolderName', anneeFolderName);
formData.set('semaineFolderName', semaineFolderName);
formData.set('nomFichier', nomFichier);
formData.set('fichier', byte);
return this.httpClient.post(this.urlUploadFile, formData);
}
当使用 spring cloud netflix 的 feign 客户端从一个微服务向另一个微服务发出 post 请求时,我在 Postman 中收到以下错误:
{
"timestamp": 1506933777413,
"status": 500,
"error": "Internal Server Error",
"exception": "feign.codec.EncodeException",
"message": "Could not write JSON: No serializer found for class java.io.FileDescriptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS); nested exception is com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class java.io.FileDescriptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile[\"inputStream\"]->java.io.FileInputStream[\"fd\"])",
"path": "/attachments"
}
我的 eclipse 控制台显示以下异常:
com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class java.io.FileDescriptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile["inputStream"]->java.io.FileInputStream["fd"]) at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:284) ~[jackson-databind-2.8.9.jar:2.8.9] at com.fasterxml.jackson.databind.SerializerProvider.mappingException(SerializerProvider.java:1110) ~[jackson-databind-2.8.9.jar:2.8.9] at com.fasterxml.jackson.databind.SerializerProvider.reportMappingProblem(SerializerProvider.java:1135) ~[jackson-databind-2.8.9.jar:2.8.9] at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.failForEmpty(UnknownSerializer.java:69) ~[jackson-databind-2.8.9.jar:2.8.9] at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.serialize(UnknownSerializer.java:32) ~[jackson-databind-2.8.9.jar:2.8.9] at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:704) ~[jackson-databind-2.8.9.jar:2.8.9] at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:689) ~[jackson-databind-2.8.9.jar:2.8.9] at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155) ~[jackson-databind-2.8.9.jar:2.8.9] at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:704) ~[jackson-databind-2.8.9.jar:2.8.9] at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:689) ~[jackson-databind-2.8.9.jar:2.8.9] at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155) ~[jackson-databind-2.8.9.jar:2.8.9] at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:292) ~[jackson-databind-2.8.9.jar:2.8.9] at com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1429) ~[jackson-databind-2.8.9.jar:2.8.9] at com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:951) ~[jackson-databind-2.8.9.jar:2.8.9]
更新 1
这是我的假界面:
@FeignClient(name="attachment-service", fallback=AttachmentHystrixFallback.class)
public interface AttachmentFeignClient {
@RequestMapping("upload")
void upload(@RequestPart(name="file") MultipartFile file, @RequestParam(name="attachableId") Long attachableId,
@RequestParam(name="className") String className, @RequestParam(name="appName") String appName);
这是主要的微服务控制器:
@RestController
public class AttachmentController implements Serializable {
/**
*
*/
private static final long serialVersionUID = -4431842080646836475L;
@Autowired
AttachmentService attachmentService;
@RequestMapping(value = "attachments", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public void upload(@RequestPart MultipartFile file, @RequestParam Long attachableId, @RequestParam String className, @RequestParam String appName) throws Exception {
attachmentService.uploadFile(file, attachableId, className, appName);
}
}
我肯定在这里缺少某种序列化器
任何建议,将不胜感激!
谢谢
在寻找解决方案几天后,我找到了这个。您应该开始为 spring 依赖项添加伪装形式:
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form-spring</artifactId>
<version>3.3.0</version>
</dependency
那么你的假客户需要这个 spring 表单编码器:
@FeignClient(
name="attachment-service",
configuration = {AttachmentFeignClient.MultipartSupportConfig.class}
fallback=AttachmentHystrixFallback.class)
public interface AttachmentFeignClient {
@RequestMapping(value= {"upload"}, consumes = {"multipart/form-data"})
void upload(
@RequestPart(name="file") MultipartFile file,
@RequestParam(name="attachableId") Long attachableId,
@RequestParam(name="className") String className,
@RequestParam(name="appName") String appName);
public class MultipartSupportConfig {
@Bean
@Primary
@Scope("prototype")
public Encoder feignFormEncoder() {
return new SpringFormEncoder();
}
}
}
希望对大家有所帮助。
更新 13/01/2020
配置 FeignClient 以使用 @RequestPart
处理 MultiPartFile
请注意,此方法仅适用于 one RequestPart 表示整个正文的情况。拥有多个 RequestPart 是 FeignClient 的另一个问题。
feignclient 的配置与@marting-choraine 所做的类似,但是通过在FeignConfigurationClass 中添加额外的编码器配置。
因此,对于我在下面提供的相同示例,您需要执行类似这样的操作,而不是实现自定义映射器
@Configuration
@EnableFeignClients(basePackages = "YourPackage")
public class FeignConfiguration {
@Bean
public Encoder feignFormEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
}
TL;DR
将您的 MultiPartFile 转换为 MultiValueMap。请参阅下面的示例
@martin-choraine 提到的答案是拥有 FeignClient 方法签名的正确和最佳答案,与您尝试调用的实际端点签名相同。但是,有一种方法不需要您定义 FormEncoder 或添加任何额外的依赖项,因为在某些应用程序中您不允许这样做(企业狗屎);您所需要的只是将您的 MultipartFile 转换为 MultiValueMap,它将完美地工作,因为标准编码器将能够对其进行序列化。
要调用的实际端点
@PostMapping(path = "/add", consumes = MULTIPART_FORM_DATA_VALUE, produces = APPLICATION_JSON_VALUE)
public MyResponseObject add(@RequestParam(name = "username") String username,
@RequestPart(name = "filetoupload") MultipartFile file) {
Do something
}
FeignClient 中的 POST 方法应该如下所示
@PostMapping(path = "/myApi/add", consumes = MULTIPART_FORM_DATA_VALUE,
produces = APPLICATION_JSON_VALUE)
public MyResponseObject addFile(@RequestParam(name = "username") String username,
@RequestPart(name = "filetoupload") MultiValueMap<String, Object> file);
然后当您调用 addFile 时,您应该像这样提供 MultiValueMap
public MyResponseObject addFileInAnotherEndPoint(String username, MultipartFile file) throws IOException {
MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>();
ByteArrayResource contentsAsResource = new ByteArrayResource(file.getBytes()) {
@Override
public String getFilename() {
return file.getOriginalFilename();
}
};
multiValueMap.add("filetoupload", contentsAsResource);
multiValueMap.add("fileType", file.getContentType());
return this.myFeignClient.addFile(username, multiValueMap);
}
我在 post 中添加了 consumes = MediaType.MULTIPART_FORM_DATA_VALUE,它对我有用。 这是我的假客户端方法,在前面我使用了 formdata
@PostMapping(path=Urls.UPLOAD_FILE_IN_LIBELLE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE )
public void uploadFileInLibelle(
@RequestParam String processus,
@RequestParam String level0Name,
@RequestParam String nomFichier,
@RequestParam String nomLibelle,
@RequestParam String anneeFolderName,
@RequestParam String semaineFolderName,
@RequestPart MultipartFile fichier);
这是我的 angular 前台
public uploadFiles(
nomFichier: string,
nomLibelle: string,
processus: string,
level0Name: string,
semaineFolderName: string,
anneeFolderName: string,
byte: File
): Observable<any> {
const formData = new FormData();
formData.set('processus', processus);
formData.set('level0Name', level0Name);
formData.set('nomLibelle', nomLibelle);
formData.set('anneeFolderName', anneeFolderName);
formData.set('semaineFolderName', semaineFolderName);
formData.set('nomFichier', nomFichier);
formData.set('fichier', byte);
return this.httpClient.post(this.urlUploadFile, formData);
}