Use feignClient to upload and download files, and compatible with data transmission
- The communication between microservices can use feign interface to communicate and transfer data content, but if there is file transfer between services, it will be very clumsy to use httpClient to transfer.
- feign interface actually transmits data through http request, so it should also be able to transmit files. The following is my implementation process and summary.
The environment is as follows:
<spring.cloud.version>Hoxton.SR7</spring.cloud.version> <com.alibaba.cloud>2.2.1.RELEASE</com.alibaba.cloud> <spring.boot.version>2.3.2.RELEASE</spring.boot.version>
- Add FeignClient dependency of spring cloud:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
Here, you only need to add this dependency, which contains the required dependencies:
<dependency> <groupId>io.github.openfeign.form</groupId> <artifactId>feign-form</artifactId> <version>3.0.3</version> </dependency> <dependency> <groupId>io.github.openfeign.form</groupId> <artifactId>feign-form-spring</artifactId> <version>3.0.3</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.3</version> </dependency>
- Create feign interface transmission file configuration class, define form encoder and inject message converter (for compatible data transmission):
import feign.codec.Encoder; import feign.form.spring.SpringFormEncoder; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.cloud.openfeign.support.SpringEncoder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FeignFileConfig { @Autowired private ObjectFactory<HttpMessageConverters> messageConverters; /** * Support feign to transfer files and data < be > * not a type supported by this encoder * @return */ @Bean public Encoder feignFormEncoder() { return new SpringFormEncoder(new SpringEncoder(messageConverters)); } }
- Create feign interface, which uses the configuration class defined in step 2 to write upload and download interfaces:
@FeignClient(value = "server2", configuration = FeignFileConfig.class) public interface TestFeign { /** * 1. File download * * @param guid * @return */ @GetMapping(value = "/file/downfile", consumes = MediaType.APPLICATION_PROBLEM_JSON_VALUE) Response downfile(@RequestParam("guid") String guid); /** * Download the file to the specified directory through guid * * @param guid * @param finalPath * @return */ default boolean downFile(@NotBlank String guid, @NotBlank String finalPath) { InputStream inputStream = null; try { //Receive the interface response with feign.Response and take out the data stream from the response Response response = this.downfile(guid); Response.Body body = response.body(); inputStream = body.asInputStream(); } catch (IOException e) { e.printStackTrace(); } //Writes the data in the stream to a file FileUtil.writeFromStream(inputStream, finalPath); if (FileUtil.exist(finalPath)) { System.out.println("------------------>feign Interface file downloaded successfully,path=" + finalPath); return true; } return false; } /** * Query whether the attachment already exists in server2 environment * ResponseDto Custom return data wrapper object * @param paths * @return Returns a collection of paths that do not exist */ @PostMapping("/checkAttachesExists") ResponseDto checkAttachesExists(@RequestBody List<String> paths); /** * Query whether the attachment already exists in server2 environment * * @param paths * @return Returns a collection of paths that do not exist */ default List<String> defaultCheckAttachesExists(List<String> paths) { ResponseDto responseDto = this.checkAttachesExists(paths); if (responseDto.isSuccess()) { //Convert the data in the returned object to the corresponding type and take it out return responseDto.getRealListData(String.class); } return Collections.EMPTY_LIST; } /** * Transfer files to server2 * * @param file * @return */ @PostMapping(value = "/receiveAttaches", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) ResponseDto sendFile(@RequestPart("file") MultipartFile file); /** * Transfer files to server2 * * @param filePath * @return Returns the file storage path returned by server2 */ default void defaultSendFile(@NotNull String filePath) { //MultipartFile that converts a file to binary data for transfer MultipartFile file = com.test.util.FileUtil.getMultipartFile(filePath, "file"); //Transmit and return data ResponseDto responseDto = this.sendFile(file); /** * Customized business operations */ } }
- To transfer a file to a MultipartFile tool class:
import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.StrUtil; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.springframework.http.MediaType; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.commons.CommonsMultipartFile; import javax.validation.constraints.NotNull; import java.io.*; public class FileUtil { /** * file To MultipartFile * @param filePath File path * @param paramName Parameter name * @return */ public static MultipartFile getMultipartFile(@NotNull final String filePath, @NotNull String paramName) { if (!cn.hutool.core.io.FileUtil.exist(filePath)) { return null; } return getMultipartFile(new File(filePath), paramName); } public static MultipartFile getMultipartFile(final File file, String paramName) { if (StrUtil.isBlank(paramName)) { paramName = "file"; } FileItem fileItem = new DiskFileItemFactory() .createItem(paramName, MediaType.MULTIPART_FORM_DATA_VALUE, true, file.getName()); try { InputStream is = new FileInputStream(file); OutputStream os = fileItem.getOutputStream(); IoUtil.copy(is, os); } catch (Exception e) { e.printStackTrace(); } return new CommonsMultipartFile(fileItem); } }
- Call:
Directly inject this FeignClient to use:
@Autowired private ExchangeFeign exchangeFeign;
- analysis:
- Whether it's an upload or download interface,
consumes = MediaType.APPLICATION_PROBLEM_JSON_VALUE
Are necessary.
- The download interface receives the data stream in the response with feign.Response, and then takes out the data from the stream
- When debugging the file download interface in the debug mode, the exception of closing the steam is close stream may occur. Just start it directly
- The file parameters of the upload interface must be modified with @ RequestPart("xxx"). If the file is transmitted with data (body data cannot be transmitted), the data needs to be modified with @ RequestParam("xxx"), such as:
@PostMapping(value = "/sendFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) ResponseDto sendFile(@RequestPart("source") MultipartFile file, @RequestParam("tradPlat") String tradPlat);
- The service provider of the upload file interface can still use @ RequestParam("xxx") to receive the file uploaded by the feign interface. According to the information found before, @ RequestPart("xxx") is also used to receive files. Here, it is specially tested that @ RequestParam is also acceptable. After all, @ RequestParam uses more.
- The response time of the interface can be extended to avoid reading timeout