微服務之間如何通過feign調用接口上傳文件
具體需求:
我們的項目是基於springboot框架的springcloud微服務搭建的,後端服務技術層面整體上分為business服務和core服務,business服務用於作為應用層,直接連接客戶端,通常用於聚合數據,core服務用來客戶端具體操作不同需求來控制數據庫,文件上傳是通過客戶端上傳接口,通過business服務,由服務端調用feign接口,也是第一次做這種文件中轉,遇到各種問題,下面是我自己的解決方案,不喜勿噴,代碼小白一枚;
一、core服務層接口@requestmapping
屬性加上consumes=MediaType.MULTIPART_FORM_DATA_VALUE如下代碼
@PostMapping(value = "/upload",consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @ResponseBody public Result<TbFile> upload(@RequestPart(value = "file",required = true) MultipartFile file, @RequestParam(name = "id",required = true) Integer id, @RequestParam(name = "desc",required = false) String desc, @RequestParam(name = "fileId",required = false) Integer fileId )
解釋:@RequestMapping存在以下兩個屬性:
1.String[] consumes() default {};
2.String[] produces() default {};
兩個屬性的解釋及參考例子:
① 屬性produces:指定返回值類型,並且可以設置返回值類型和返回值的字符編碼;代碼例子參考如下:
屬性produces=”application/json”時,返回json數據
屬性produces=”MediaType.APPLICATION_JSON_VALUE;charset=utf-8″時,設置返回數據的字符編碼為utf-8
@Controller @RequestMapping(value = "/getperson", method = RequestMethod.GET, produces="application/json") public Object getPerson(int id) { //實現自己的邏輯調用 Person p= new person(); return p; }
特別說明:produces=”application/json”和註解@ResponseBody是一樣的效果,使用瞭註解其實可以不使用該屬性瞭
② 屬性consumes: 指定處理請求當中的提交內容類型(Content-Type):application/json, text/html等;
代碼例子參考如下:
@PostMapping(value = "/upload",consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @ResponseBody public Result<TbFile> upload(@RequestPart(value = "file",required = true) MultipartFile file, @RequestParam(name = "id",required = true) Integer id, @RequestParam(name = "desc",required = false) String desc, @RequestParam(name = "fileId",required = false) Integer fileId ){ }
解釋: MediaType.MULTIPART_FORM_DATA_VALUE 代表的值為multipart/form-data它會將表單的數據處理為一條消息,以標簽為單元,用分隔符分開。既可以上傳鍵值對,也可以上傳文件。當上傳的字段是文件時,會有Content-Type來表名文件類型;content-disposition,用來說明字段的一些信息;
二、business客戶層接口@requestmapping
屬性加上consumes=MediaType.MULTIPART_FORM_DATA_VALUE如下代碼
@PostMapping(value = "/upload",produces = MediaType.APPLICATION_JSON_UTF8_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @ResponseBody Result<TbFile> upload(@RequestPart(value = "file",required = true) MultipartFile file, @RequestParam(name = "id",required = true) Integer id, @RequestParam(name = "desc",required = false) String desc, @RequestParam(name = "fileId",required = false) Integer fileId );
具體大概就這麼多。能力有限,多多指教!!!
feign微服務間文件上傳(Finchley版本)
在Spring Cloud 的Feign組件中並不支持文件的傳輸,會出現這樣的錯誤提示:
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]
但是我們可以通過使用Feign的擴展包實現這個功能。
一. 示例介紹
服務名 | 端口號 | 角色 |
---|---|---|
feign_upload_first | 8100 | feign服務提供者 |
feign_upload_second | 8101 | feign服務消費者 |
我們調用feign_upload_second的上傳文件接口上傳文件,feign_upload_second內部使用feign調用feign_upload_first實現文件上傳。
二 、單文件上傳
2.1 feign_upload_first服務提供者
文件上傳的服務提供者接口比較簡單,如下所示:
@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服務消費者
增加擴展包依賴
<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實現文件上傳的配置類
@Configuration public class FeignSupportConfig { @Bean public Encoder feignFormEncoder() { return new SpringFormEncoder(); } }
feign遠程調用接口
@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 測試
使用postman進行測試,可以正常上傳文件
三、多文件上傳
既然單個文件可以上傳,那麼多文件應該也沒問題吧,我們對上面的代碼進行修改
3.1 feign_upload_first服務提供者
文件上傳的服務提供者接口比較簡單,如下所示:
@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服務消費者
feign遠程調用接口
@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 測試
經過測試發現,無法上傳多個文件。經過檢查,發現源碼裡底層是有對MultipartFile[]類型的支持的,源碼中有個類叫SpringManyMultipartFilesWriter,是專門針對文件數組類型進行操作的,但是配置到項目裡的SpringFormEncoder類裡卻沒有對文件數組類型的判斷,以致不能支持文件數組的上傳
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類構造時把SpringManyMultipartFilesWriter實例添加到瞭處理器列表裡瞭,但是在encode方法裡又隻判斷瞭MultipartFile類型,沒有判斷數組類型,底層有對數組的支持但上層卻缺少瞭相應判斷。那麼我們可以自己去擴展FormEncoder,仿照SpringFormEncoder源碼,隻修改encode方法。
3.3 擴展FormEncoder支持多文件上傳
擴展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); } }
註冊配置類
@Configuration public class FeignSupportConfig { @Bean public Encoder feignFormEncoder() { return new FeignSpringFormEncoder(); } }
經過測試可以上傳多個文件。
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- 解決feign微服務間的文件上傳報錯問題
- Feign調用傳輸文件異常的解決
- Feign遠程調用Multipartfile參數處理
- springcloud如何使用Feign後臺內部傳遞MultipartFile
- FeignMultipartSupportConfig上傳圖片配置方式