微服务之间如何通过feign调用接口上传文件

 更新时间:2021年06月30日 11:29:01   作者:想网名太麻烦  
这篇文章主要介绍了微服务之间如何通过feign调用接口上传文件的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

具体需求:

我们的项目是基于springboot框架的springcloud微服务搭建的,后端服务技术层面整体上分为business服务和core服务,business服务用于作为应用层,直接连接客户端,通常用于聚合数据,core服务用来客户端具体操作不同需求来控制数据库,文件上传是通过客户端上传接口,通过business服务,由服务端调用feign接口,也是第一次做这种文件中转,遇到各种问题,下面是我自己的解决方案,不喜勿喷,代码小白一枚;

一、core服务层接口@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:指定返回值类型,并且可以设置返回值类型和返回值的字符编码;代码例子参考如下:

属性produces="application/json"时,返回json数据

属性produces="MediaType.APPLICATION_JSON_VALUE;charset=utf-8"时,设置返回数据的字符编码为utf-8

@Controller    
@RequestMapping(value = "/getperson", method = RequestMethod.GET, produces="application/json")    
public Object getPerson(int id) {       
    //实现自己的逻辑调用
  Person p= new person();
 
    return p;
} 

特别说明:produces="application/json"和注解@ResponseBody是一样的效果,使用了注解其实可以不使用该属性了

② 属性consumes: 指定处理请求当中的提交内容类型(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它会将表单的数据处理为一条消息,以标签为单元,用分隔符分开。既可以上传键值对,也可以上传文件。当上传的字段是文件时,会有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微服务间文件上传(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_first 8100 feign服务提供者
feign_upload_second 8101 feign服务消费者

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

增加扩展包依赖

    <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实现文件上传的配置类

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

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

上传文件接口

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

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

上传文件接口

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

扩展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();
  }
}

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

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • java 解压与压缩文件夹的实例详解

    java 解压与压缩文件夹的实例详解

    这篇文章主要介绍了 java 解压与压缩文件夹的实例详解的相关资料,希望通过本文能帮助到大家,让大家实现这样的功能,掌握这样的方法,需要的朋友可以参考下
    2017-10-10
  • Java mutable对象和immutable对象的区别说明

    Java mutable对象和immutable对象的区别说明

    这篇文章主要介绍了Java mutable对象和immutable对象的区别,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • Java使用单链表实现约瑟夫环

    Java使用单链表实现约瑟夫环

    这篇文章主要为大家详细介绍了Java使用单链表实现约瑟夫环,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-10-10
  • Java字符串常量池和intern方法解析

    Java字符串常量池和intern方法解析

    本文主要介绍了Java字符串常量池和intern方法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-05-05
  • 图文结合讲解Java单例模式

    图文结合讲解Java单例模式

    这篇文章主要以图文结合的方式为大家详细介绍了Java单例模式的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-10-10
  • SpringBoot2.0集成MQTT消息推送功能实现

    SpringBoot2.0集成MQTT消息推送功能实现

    这篇文章主要介绍了SpringBoot2.0集成MQTT消息推送功能实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • Nacos配合SpringBoot实现动态线程池的基本步骤

    Nacos配合SpringBoot实现动态线程池的基本步骤

    使用Nacos配合Spring Boot实现动态线程池,可以让你的应用动态地调整线程池参数而无需重启,这对于需要高度可配置且需要适应不同负载情况的应用来说非常有用,本文给大家介绍实现动态线程池的基本步骤,需要的朋友可以参考下
    2024-02-02
  • java数据结构实现顺序表示例

    java数据结构实现顺序表示例

    这篇文章主要介绍了java数据结构实现顺序表示例,需要的朋友可以参考下
    2014-03-03
  • JAVA实现线程的三种方法

    JAVA实现线程的三种方法

    这篇文章介绍了JAVA实现线程的三种方法,有需要的朋友可以参考一下
    2013-09-09
  • IntelliJ IDEA2021.1 配置大全(超详细教程)

    IntelliJ IDEA2021.1 配置大全(超详细教程)

    这篇文章主要介绍了IntelliJ IDEA2021.1 配置大全(超详细教程),需要的朋友可以参考下
    2021-04-04

最新评论