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_first8100feign服务提供者
feign_upload_second8101feign服务消费者

我们调用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服务消费者

1.增加扩展包依赖

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

2.新增feign实现文件上传的配置类

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

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

4.上传文件接口

@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服务消费者

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

2.上传文件接口

@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支持多文件上传

  1. 扩展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);
  }
}
  1. 注册配置类
@Configuration
public class FeignSupportConfig {
  @Bean
  public Encoder feignFormEncoder() {
    return new FeignSpringFormEncoder();
  }
}

经过测试可以上传多个文件。

 

https://www.jianshu.com/p/4f4d9d084b1d

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当使用Feign实现微服务的远程调用时,需要注意以下几个问题: 1. 服务的可用性问题:在服务之进行远程调用时,要保证被调用的服务的可用性。例如,如果一个服务不可用,那么对该服务进行远程调用就会失败。为了避免这种情况,可以使用负载均衡来确保服务的高可用性。 2. 服务的数据传输问题:在进行服务之的远程调用时,需要考虑传输的数据大小和数据格式。如果数据太大,会导致网络传输的延迟和带宽问题。为了解决这个问题,可以使用压缩技术来减小数据传输的大小。 3. 服务的版本问题:在进行服务之的远程调用时,需要考虑服务的版本问题。例如,如果调用的服务升级了版本,那么对该服务进行远程调用时,需要保证调用的版本与调用方的版本一致。为了解决这个问题,可以使用版本控制机制来管理服务的版本。 4. 服务的异常处理问题:在进行服务之的远程调用时,需要考虑异常处理问题。例如,如果调用的服务出现了异常,需要及时处理异常信息,防止异常信息影响到整个系统的运行。为了解决这个问题,可以使用异常捕获机制来捕获异常信息,并对异常信息进行处理。 为了解决上述问题,可以采用以下几个解决方案: 1. 使用负载均衡技术来确保服务的高可用性,例如使用Nginx或Zuul等网关来进行负载均衡。 2. 使用数据压缩技术来减小数据传输的大小,例如使用Gzip或Snappy等压缩算法来对数据进行压缩。 3. 使用版本控制机制来管理服务的版本,例如使用Git或SVN等版本控制系统来对服务进行版本控制。 4. 使用异常捕获机制来捕获异常信息,并对异常信息进行处理,例如使用try-catch语句来对异常进行捕获和处理。同时,还可以使用日志系统来记录异常信息,以便于后续的排查和分析。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值