使用Feign擴(kuò)展包實(shí)現(xiàn)微服務(wù)間文件上傳
在Spring Cloud 的Feign組件中并不支持文件的傳輸,會(huì)出現(xiàn)這樣的錯(cuò)誤提示:
feign.codec.EncodeException: class [Lorg.springframework.web.multipart.MultipartFile; is not a type supported by this encoder. at feign.codec.Encoder$Default.encode(Encoder.java:90) ~[feign-core-9.5.1.jar:na] at feign.form.FormEncoder.encode(FormEncoder.java:87) ~[feign-form-3.3.0.jar:3.3.0] at feign.form.spring.SpringFormEncoder.encode(SpringFormEncoder.java:64) ~[feign-form-spring-3.3.0.jar:3.3.0]
但是我們可以通過(guò)使用Feign的擴(kuò)展包實(shí)現(xiàn)這個(gè)功能。
一. 示例介紹

我們調(diào)用feign_upload_second的上傳文件接口上傳文件,feign_upload_second內(nèi)部使用feign調(diào)用feign_upload_first實(shí)現(xiàn)文件上傳。
二 、單文件上傳
2.1 feign_upload_first服務(wù)提供者
文件上傳的服務(wù)提供者接口比較簡(jiǎn)單,如下所示:
@SpringBootApplication
public class FeignUploadFirstApplication {
@RestController
public class UploadController {
@RequestMapping(value = "/uploadFile",method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String handleFileUpload(@RequestPart(value = "file") MultipartFile file) {
return file.getOriginalFilename();
}
}
public static void main(String[] args) {
SpringApplication.run(FeignUploadFirstApplication.class, args);
}
}
2.2 feign_upload_second服務(wù)消費(fèi)者
增加擴(kuò)展包依賴
<dependency> <groupId>io.github.openfeign.form</groupId> <artifactId>feign-form</artifactId> <version>3.3.0</version> </dependency> <dependency> <groupId>io.github.openfeign.form</groupId> <artifactId>feign-form-spring</artifactId> <version>3.3.0</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.3</version> </dependency>
新增feign實(shí)現(xiàn)文件上傳的配置類
@Configuration
public class FeignSupportConfig {
@Bean
public Encoder feignFormEncoder() {
return new SpringFormEncoder();
}
}
feign遠(yuǎn)程調(diào)用接口
@FeignClient(name = "file",url = "http://localhost:8100",configuration = FeignSupportConfig.class)
public interface UploadService {
@RequestMapping(value = "/uploadFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
String handleFileUpload(@RequestPart(value = "file") MultipartFile file);
}
上傳文件接口
@RestController
public class UploadController {
@Autowired
UploadService uploadService;
@RequestMapping(value = "/uploadFile",method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String handleFileUpload(@RequestPart(value = "file") MultipartFile file) {
return uploadService.handleFileUpload(file);
}
}
2.3 測(cè)試
使用postman進(jìn)行測(cè)試,可以正常上傳文件
三、多文件上傳
既然單個(gè)文件可以上傳,那么多文件應(yīng)該也沒(méi)問(wèn)題吧,我們對(duì)上面的代碼進(jìn)行修改
3.1 feign_upload_first服務(wù)提供者
文件上傳的服務(wù)提供者接口比較簡(jiǎn)單,如下所示:
@SpringBootApplication
public class FeignUploadFirstApplication {
@RestController
public class UploadController {
@RequestMapping(value = "/uploadFile",method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String handleFileUpload(@RequestPart(value = "file") MultipartFile file) {
return file.getOriginalFilename();
}
@RequestMapping(value = "/uploadFile2",method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String handleFileUpload(@RequestPart(value = "file") MultipartFile[] file) {
String fileName = "";
for(MultipartFile f : file){
fileName += f.getOriginalFilename()+"---";
}
return fileName;
}
}
public static void main(String[] args) {
SpringApplication.run(FeignUploadFirstApplication.class, args);
}
}
3.2 feign_upload_second服務(wù)消費(fèi)者
feign遠(yuǎn)程調(diào)用接口
@FeignClient(name = "file",url = "http://localhost:8100",configuration = FeignSupportConfig.class)
public interface UploadService {
@RequestMapping(value = "/uploadFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
String handleFileUpload(@RequestPart(value = "file") MultipartFile file);
@RequestMapping(value = "/uploadFile2", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
String handleFileUpload(@RequestPart(value = "file") MultipartFile[] file);
}
上傳文件接口
@RestController
public class UploadController {
@Autowired
UploadService uploadService;
@RequestMapping(value = "/uploadFile",method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String handleFileUpload(@RequestPart(value = "file") MultipartFile file) {
return uploadService.handleFileUpload(file);
}
@RequestMapping(value = "/uploadFile2",method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String handleFileUpload2(@RequestPart(value = "file") MultipartFile[] file) {
return uploadService.handleFileUpload(file);
}
}
3.3 測(cè)試
經(jīng)過(guò)測(cè)試發(fā)現(xiàn),無(wú)法上傳多個(gè)文件。經(jīng)過(guò)檢查,發(fā)現(xiàn)源碼里底層是有對(duì)MultipartFile[]類型的支持的,源碼中有個(gè)類叫SpringManyMultipartFilesWriter,是專門針對(duì)文件數(shù)組類型進(jìn)行操作的,但是配置到項(xiàng)目里的SpringFormEncoder類里卻沒(méi)有對(duì)文件數(shù)組類型的判斷,以致不能支持文件數(shù)組的上傳
SpringManyMultipartFilesWriter源碼
public class SpringManyMultipartFilesWriter extends AbstractWriter {
private final SpringSingleMultipartFileWriter fileWriter = new SpringSingleMultipartFileWriter();
public SpringManyMultipartFilesWriter() {
}
public void write(Output output, String boundary, String key, Object value) throws Exception {
if (value instanceof MultipartFile[]) {
MultipartFile[] files = (MultipartFile[])((MultipartFile[])value);
MultipartFile[] var6 = files;
int var7 = files.length;
for(int var8 = 0; var8 < var7; ++var8) {
MultipartFile file = var6[var8];
this.fileWriter.write(output, boundary, key, file);
}
} else if (value instanceof Iterable) {
Iterable<?> iterable = (Iterable)value;
Iterator var11 = iterable.iterator();
while(var11.hasNext()) {
Object file = var11.next();
this.fileWriter.write(output, boundary, key, file);
}
}
}
public boolean isApplicable(Object value) {
if (value == null) {
return false;
} else if (value instanceof MultipartFile[]) {
return true;
} else {
if (value instanceof Iterable) {
Iterable<?> iterable = (Iterable)value;
Iterator<?> iterator = iterable.iterator();
if (iterator.hasNext() && iterator.next() instanceof MultipartFile) {
return true;
}
}
return false;
}
}
}
SpringFormEncoder源碼
public class SpringFormEncoder extends FormEncoder {
public SpringFormEncoder() {
this(new Default());
}
public SpringFormEncoder(Encoder delegate) {
super(delegate);
MultipartFormContentProcessor processor = (MultipartFormContentProcessor)this.getContentProcessor(ContentType.MULTIPART);
processor.addWriter(new SpringSingleMultipartFileWriter());
processor.addWriter(new SpringManyMultipartFilesWriter());
}
public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
if (!bodyType.equals(MultipartFile.class)) {
super.encode(object, bodyType, template);
} else {
MultipartFile file = (MultipartFile)object;
Map<String, Object> data = Collections.singletonMap(file.getName(), object);
super.encode(data, MAP_STRING_WILDCARD, template);
}
}
}
從上面SpringFormEncoder的源碼上可以看到SpringFormEncoder類構(gòu)造時(shí)把SpringManyMultipartFilesWriter實(shí)例添加到了處理器列表里了,但是在encode方法里又只判斷了MultipartFile類型,沒(méi)有判斷數(shù)組類型,底層有對(duì)數(shù)組的支持但上層卻缺少了相應(yīng)判斷。那么我們可以自己去擴(kuò)展FormEncoder,仿照SpringFormEncoder源碼,只修改encode方法。
3.3 擴(kuò)展FormEncoder支持多文件上傳
擴(kuò)展FormEncoder,命名為FeignSpringFormEncoder
public class FeignSpringFormEncoder extends FormEncoder {
/**
* Constructor with the default Feign's encoder as a delegate.
*/
public FeignSpringFormEncoder() {
this(new Default());
}
/**
* Constructor with specified delegate encoder.
*
* @param delegate delegate encoder, if this encoder couldn't encode object.
*/
public FeignSpringFormEncoder(Encoder delegate) {
super(delegate);
MultipartFormContentProcessor processor = (MultipartFormContentProcessor) getContentProcessor(ContentType.MULTIPART);
processor.addWriter(new SpringSingleMultipartFileWriter());
processor.addWriter(new SpringManyMultipartFilesWriter());
}
@Override
public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
if (bodyType.equals(MultipartFile.class)) {
MultipartFile file = (MultipartFile) object;
Map data = Collections.singletonMap(file.getName(), object);
super.encode(data, MAP_STRING_WILDCARD, template);
return;
} else if (bodyType.equals(MultipartFile[].class)) {
MultipartFile[] file = (MultipartFile[]) object;
if(file != null) {
Map data = Collections.singletonMap(file.length == 0 ? "" : file[0].getName(), object);
super.encode(data, MAP_STRING_WILDCARD, template);
return;
}
}
super.encode(object, bodyType, template);
}
}
注冊(cè)配置類
@Configuration
public class FeignSupportConfig {
@Bean
public Encoder feignFormEncoder() {
return new FeignSpringFormEncoder();
}
}
經(jīng)過(guò)測(cè)試可以上傳多個(gè)文件。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
使用HttpServletResponse對(duì)象獲取請(qǐng)求行信息
這篇文章主要介紹了使用HttpServletResponse對(duì)象獲取請(qǐng)求行信息,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02
springboot prototype設(shè)置多例不起作用的解決操作
這篇文章主要介紹了springboot prototype設(shè)置多例不起作用的解決操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09
運(yùn)行Jar包出現(xiàn)提示xxx中沒(méi)有主清單屬性報(bào)錯(cuò)問(wèn)題解決方法
這篇文章主要介紹了運(yùn)行Jar包出現(xiàn):xxx中沒(méi)有主清單屬性報(bào)錯(cuò),當(dāng)出現(xiàn)報(bào)錯(cuò):xxx中沒(méi)有主清單屬性,解決方法也很簡(jiǎn)單,在pom.xml配置中,加上相應(yīng)配置即可,需要的朋友可以參考下2023-08-08
spring-boot整合dubbo:Spring-boot-dubbo-starter
這篇文章主要介紹了spring-boot整合dubbo:Spring-boot-dubbo-starter的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-05-05
使用Spring Batch實(shí)現(xiàn)批處理任務(wù)的詳細(xì)教程
在企業(yè)級(jí)應(yīng)用中,批處理任務(wù)是不可或缺的一部分,它們通常用于處理大量數(shù)據(jù),如數(shù)據(jù)遷移、數(shù)據(jù)清洗、生成報(bào)告等,Spring Batch是Spring框架的一部分,本文將介紹如何使用Spring Batch與SpringBoot結(jié)合,構(gòu)建和管理批處理任務(wù),需要的朋友可以參考下2024-06-06
Spring動(dòng)態(tài)數(shù)據(jù)源實(shí)現(xiàn)讀寫分離詳解
這篇文章主要為大家詳細(xì)介紹了Spring動(dòng)態(tài)數(shù)據(jù)源實(shí)現(xiàn)讀寫分離,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
SpringBoot 注解事務(wù)聲明式事務(wù)的方式
springboot使用上述注解的幾種方式開(kāi)啟事物,可以達(dá)到和xml中聲明的同樣效果,但是卻告別了xml,使你的代碼遠(yuǎn)離配置文件。今天就扒一扒springboot中事務(wù)使用注解的玩法,感興趣的朋友一起看看吧2017-09-09

