SpringCloud之FeignClient文件上传下载

使用feignClient实现文件上传下载,并兼容数据传输

  • 微服务之间的通信可以使用feign接口进行通信,传输数据内容,但是服务之间如果有文件传输时,如果再去使用httpClient传输,就会显得很笨拙。
  • feign接口实际上也是通过http请求传输数据,那么它就应该也可以传输文件,以下是我的实现过程以及遇坑总结。


    环境如下:
<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>
  1. 添加SpringCloud的FeignClient依赖
<dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

这里只要添加这一个依赖即可,它里面包含了需要的依赖:

    <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>
  1. 创建feign接口传输文件配置类,定义表单编码器,注入消息转换器(为了兼容数据传输)
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;

    /**
     * 支持feign传输文件和数据<be>
     * 解决在传输数据时报错not a type supported by this encoder
     * @return
     */
    @Bean
    public Encoder feignFormEncoder() {
        return new SpringFormEncoder(new SpringEncoder(messageConverters));
    }
}
  1. 创建feign接口,接口使用第二步定义的配置类,编写上传和下载的接口
@FeignClient(value = "server2", configuration = FeignFileConfig.class)
public interface TestFeign {
	/**
     * 1. 文件下载
     *
     * @param guid
     * @return
     */
    @GetMapping(value = "/file/downfile", consumes = MediaType.APPLICATION_PROBLEM_JSON_VALUE)
    Response downfile(@RequestParam("guid") String guid);

    /**
     * 通过guid将文件下载至指定目录
     *
     * @param guid
     * @param finalPath
     * @return
     */
    default boolean downFile(@NotBlank String guid, @NotBlank String finalPath) {
        InputStream inputStream = null;
        try {
        	//用feign.Response接收接口响应,从响应中取出数据流
            Response response = this.downfile(guid);
            Response.Body body = response.body();
            inputStream = body.asInputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
        //将流中的数据写入文件
        FileUtil.writeFromStream(inputStream, finalPath);
        if (FileUtil.exist(finalPath)) {
            System.out.println("------------------>feign接口文件下载成功,path=" + finalPath);
            return true;
        }
        return false;
    }

    /**
     * 查询附件在server2环境中是否已经存在
     * ResponseDto 自定义的返回数据包装对象
     * @param paths
     * @return 返回不存在路径集合
     */
    @PostMapping("/checkAttachesExists")
    ResponseDto checkAttachesExists(@RequestBody List<String> paths);

    /**
     * 查询附件在server2环境中是否已经存在
     *
     * @param paths
     * @return 返回不存在路径集合
     */
    default List<String> defaultCheckAttachesExists(List<String> paths) {
        ResponseDto responseDto = this.checkAttachesExists(paths);
        if (responseDto.isSuccess()) {
        	//将返回对象中的数据转成对应的类型并取出
            return responseDto.getRealListData(String.class);
        }
        return Collections.EMPTY_LIST;
    }

    /**
     * 向server2传输文件
     *
     * @param file
     * @return
     */
    @PostMapping(value = "/receiveAttaches", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    ResponseDto sendFile(@RequestPart("file") MultipartFile file);

    /**
     * 向server2传输文件
     *
     * @param filePath
     * @return 返回server2返回的文件存放路径
     */
    default void defaultSendFile(@NotNull String filePath) {
    	//将文件转换为二进制数据的MultipartFile用于传输
        MultipartFile file = com.test.util.FileUtil.getMultipartFile(filePath, "file");
        //进行传输并返回数据
        ResponseDto responseDto = this.sendFile(file);
        /**
        * 自定义的业务操作
        */
    }
}
  • 将文件转MultipartFile工具类
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转MultipartFile
     * @param filePath 文件路径
     * @param paramName 参数名
     * @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);
    }
}
  1. 调用
    直接注入这个FeignClient使用:
@Autowired
private ExchangeFeign exchangeFeign;
  1. 分析
  • 不管是上传还是下载接口,consumes = MediaType.APPLICATION_PROBLEM_JSON_VALUE都是必须的。

  • 下载接口用feign.Response接收响应中的数据流,然后从流中取出数据即可

  • 在使用debug模式下调试文件下载接口时可能会出现steam is close流关闭的异常,直接启动就好了

  • 上传接口的文件参数必须用@RequestPart("xxx")修饰,如果传输文件的同时还传输数据(不能传输body数据),数据需要用@RequestParam("xxx")修饰,如:


@PostMapping(value = "/sendFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
ResponseDto sendFile(@RequestPart("source") MultipartFile file, @RequestParam("tradPlat") String tradPlat);
    
  • 上传文件接口的服务提供方,接收文件的参数仍然可以用@RequestParam("xxx")接收feign接口上传的文件。之前查找的资料中说接收文件也要用@RequestPart("xxx"),这里特地测试了一下,@RequestParam也是可以接收的,毕竟@RequestParam使用的更多一些。
  • 接口的响应时间可以延长一些,避免读取超时
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值