How to upload files between microservices through feign call interface

  • 2021-10-15 10:29:21
  • OfStack

Specific needs:

Our project is based on springcloud microservice of springboot framework. The technical level of back-end services is divided into business services and core services as a whole, The business service is used as an application layer, Direct connection to the client, usually used to aggregate data, core service is used to control the database according to different requirements of the specific operation of the client, file upload is through the client upload interface, through business service, and the server calls feign interface, which is also the first time to do this kind of file transfer, and encounter various problems. The following is my own solution, don't spray it if you don't like it, and the code is small white;

1. core service layer interface @ requestmapping

Attribute plus 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 )

Explanation: @ RequestMapping has the following two properties:

1.String[] consumes() default {};

2.String[] produces() default {};

Explanation and reference examples of two attributes:

① Attribute produces: Specifies the return value type, and can set the return value type and the character code of the return value; Refer to the following code examples:

json data is returned when the attribute produces= "application/json"

Attribute produces= "MediaType.APPLICATION_JSON_VALUE; When charset=utf-8 ", set the character code of the returned data to utf-8


@Controller    
@RequestMapping(value = "/getperson", method = RequestMethod.GET, produces="application/json")    
public Object getPerson(int id) {       
    // Implement your own logical call 
  Person p= new person();
 
    return p;
} 

Special note: produces= "application/json" and annotation @ ResponseBody have the same effect. If annotation is used, it is unnecessary to use this attribute

② Attribute consumes: Specify the submission content types in the processing request (Content-Type): application/json, text/html, etc.;

Refer to the following code examples:


 @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 ){
}

Explanation: MediaType. MULTIPART_FORM_DATA_VALUE represents a value of multipart/form-data. It treats the form's data as a message, separated by a separator in units of labels. You can upload both key-value pairs and files. When the uploaded field is a file, there will be Content-Type to list the file type; content-disposition, which is used to explain 1 information of the field;

2. business Client Layer Interface @ requestmapping

Attribute plus the following code for 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 );

That's about it. Limited ability, give me more advice! ! !

feign inter-microservice file upload (Finchley version)

File transfer is not supported in the Feign component of Spring Cloud and an error message appears:

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]

However, we can achieve this function by using the extension pack of Feign.

1. Example introduction

服务名 端口号 角色
feign_upload_first 8100 feign服务提供者
feign_upload_second 8101 feign服务消费者

We call the upload file interface of feign_upload_second to upload files, and feign_upload_second uses feign to call feign_upload_first to upload files.

2. Single file upload

2.1 feign_upload_first Service Provider

The service provider interface for file upload is relatively simple, as follows:


@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 Service Consumer

Adding extension pack dependencies


    <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>

New configuration class for feign to upload files


@Configuration
public class FeignSupportConfig {
  @Bean
  public Encoder feignFormEncoder() {
    return new SpringFormEncoder();
  }
}

feign Remote Call Interface


@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);
}

Upload file interface


@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 Test

Using postman for testing, files can be uploaded normally

3. Upload multiple files

Since a single file can be uploaded, so many files should be no problem, let's modify the above code

3.1 feign_upload_first Service Provider

The service provider interface for file upload is relatively simple, as follows:


@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 Service Consumers

feign Remote Call Interface


@Controller    
@RequestMapping(value = "/getperson", method = RequestMethod.GET, produces="application/json")    
public Object getPerson(int id) {       
    // Implement your own logical call 
  Person p= new person();
 
    return p;
} 
0

Upload file interface


@Controller    
@RequestMapping(value = "/getperson", method = RequestMethod.GET, produces="application/json")    
public Object getPerson(int id) {       
    // Implement your own logical call 
  Person p= new person();
 
    return p;
} 
1

3.3 Testing

After testing, it was found that multiple files could not be uploaded. After checking, found that the bottom layer of the source code is to support MultipartFile [] type, source code has a class called SpringManyMultipartFilesWriter, is specifically for the file array type operation, but configuration to the project in the SpringFormEncoder class does not have the judgment of the file array type, so that can not support the upload of the file array

SpringManyMultipartFilesWriter source code


@Controller    
@RequestMapping(value = "/getperson", method = RequestMethod.GET, produces="application/json")    
public Object getPerson(int id) {       
    // Implement your own logical call 
  Person p= new person();
 
    return p;
} 
2

SpringFormEncoder source code


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);
    }
  }
}

From the source code of SpringFormEncoder above, we can see that SpringFormEncoder class construction adds SpringManyMultipartFilesWriter instance to the processor list, but only judges MultipartFile type in encode method, and does not judge array type. The bottom layer has support for array but the upper layer lacks corresponding judgment. Then we can extend the FormEncoder by ourselves, imitate the source code of SpringFormEncoder, and only modify the encode method.

3.3 Extend FormEncoder to support multiple file uploads

Extended FormEncoder, named FeignSpringFormEncoder


@Controller    
@RequestMapping(value = "/getperson", method = RequestMethod.GET, produces="application/json")    
public Object getPerson(int id) {       
    // Implement your own logical call 
  Person p= new person();
 
    return p;
} 
4

Register configuration classes


@Controller    
@RequestMapping(value = "/getperson", method = RequestMethod.GET, produces="application/json")    
public Object getPerson(int id) {       
    // Implement your own logical call 
  Person p= new person();
 
    return p;
} 
5

After testing, you can upload multiple files.


Related articles: