微服務(wù)之間如何通過feign調(diào)用接口上傳文件
具體需求:
我們的項目是基于springboot框架的springcloud微服務(wù)搭建的,后端服務(wù)技術(shù)層面整體上分為business服務(wù)和core服務(wù),business服務(wù)用于作為應(yīng)用層,直接連接客戶端,通常用于聚合數(shù)據(jù),core服務(wù)用來客戶端具體操作不同需求來控制數(shù)據(jù)庫,文件上傳是通過客戶端上傳接口,通過business服務(wù),由服務(wù)端調(diào)用feign接口,也是第一次做這種文件中轉(zhuǎn),遇到各種問題,下面是我自己的解決方案,不喜勿噴,代碼小白一枚;
一、core服務(wù)層接口@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:指定返回值類型,并且可以設(shè)置返回值類型和返回值的字符編碼;代碼例子參考如下:
屬性produces="application/json"時,返回json數(shù)據(jù)
屬性produces="MediaType.APPLICATION_JSON_VALUE;charset=utf-8"時,設(shè)置返回數(shù)據(jù)的字符編碼為utf-8
@Controller
@RequestMapping(value = "/getperson", method = RequestMethod.GET, produces="application/json")
public Object getPerson(int id) {
//實現(xiàn)自己的邏輯調(diào)用
Person p= new person();
return p;
}
特別說明:produces="application/json"和注解@ResponseBody是一樣的效果,使用了注解其實可以不使用該屬性了
② 屬性consumes: 指定處理請求當(dāng)中的提交內(nèi)容類型(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它會將表單的數(shù)據(jù)處理為一條消息,以標(biāo)簽為單元,用分隔符分開。既可以上傳鍵值對,也可以上傳文件。當(dāng)上傳的字段是文件時,會有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微服務(wù)間文件上傳(Finchley版本)
在Spring Cloud 的Feign組件中并不支持文件的傳輸,會出現(xiàn)這樣的錯誤提示:
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的擴(kuò)展包實現(xiàn)這個功能。
一. 示例介紹
| 服務(wù)名 | 端口號 | 角色 |
|---|---|---|
| feign_upload_first | 8100 | feign服務(wù)提供者 |
| feign_upload_second | 8101 | feign服務(wù)消費(fèi)者 |
我們調(diào)用feign_upload_second的上傳文件接口上傳文件,feign_upload_second內(nèi)部使用feign調(diào)用feign_upload_first實現(xiàn)文件上傳。
二 、單文件上傳
2.1 feign_upload_first服務(wù)提供者
文件上傳的服務(wù)提供者接口比較簡單,如下所示:
@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實現(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 測試
使用postman進(jìn)行測試,可以正常上傳文件
三、多文件上傳
既然單個文件可以上傳,那么多文件應(yīng)該也沒問題吧,我們對上面的代碼進(jìn)行修改
3.1 feign_upload_first服務(wù)提供者
文件上傳的服務(wù)提供者接口比較簡單,如下所示:
@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 測試
經(jīng)過測試發(fā)現(xiàn),無法上傳多個文件。經(jīng)過檢查,發(fā)現(xiàn)源碼里底層是有對MultipartFile[]類型的支持的,源碼中有個類叫SpringManyMultipartFilesWriter,是專門針對文件數(shù)組類型進(jìn)行操作的,但是配置到項目里的SpringFormEncoder類里卻沒有對文件數(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)造時把SpringManyMultipartFilesWriter實例添加到了處理器列表里了,但是在encode方法里又只判斷了MultipartFile類型,沒有判斷數(shù)組類型,底層有對數(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);
}
}
注冊配置類
@Configuration
public class FeignSupportConfig {
@Bean
public Encoder feignFormEncoder() {
return new FeignSpringFormEncoder();
}
}
經(jīng)過測試可以上傳多個文件。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java mutable對象和immutable對象的區(qū)別說明
這篇文章主要介紹了Java mutable對象和immutable對象的區(qū)別,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06
SpringBoot2.0集成MQTT消息推送功能實現(xiàn)
這篇文章主要介紹了SpringBoot2.0集成MQTT消息推送功能實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
Nacos配合SpringBoot實現(xiàn)動態(tài)線程池的基本步驟
使用Nacos配合Spring Boot實現(xiàn)動態(tài)線程池,可以讓你的應(yīng)用動態(tài)地調(diào)整線程池參數(shù)而無需重啟,這對于需要高度可配置且需要適應(yīng)不同負(fù)載情況的應(yīng)用來說非常有用,本文給大家介紹實現(xiàn)動態(tài)線程池的基本步驟,需要的朋友可以參考下2024-02-02
java數(shù)據(jù)結(jié)構(gòu)實現(xiàn)順序表示例
這篇文章主要介紹了java數(shù)據(jù)結(jié)構(gòu)實現(xiàn)順序表示例,需要的朋友可以參考下2014-03-03
IntelliJ IDEA2021.1 配置大全(超詳細(xì)教程)
這篇文章主要介紹了IntelliJ IDEA2021.1 配置大全(超詳細(xì)教程),需要的朋友可以參考下2021-04-04

