spring cloud feign (包含上传文件和下载文件)

fat_whale

定位:官网摘的

Feign is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has pluggable annotation support including Feign annotations and JAX-RS annotations. Feign also supports pluggable encoders and decoders. Spring Cloud adds support for Spring MVC annotations and for using the same HttpMessageConverters used by default in Spring Web. Spring Cloud integrates Ribbon and Eureka to provide a load balanced http client when using Feign.

如何使用:

  • To include Feign in your project
<!--https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    <version>2.2.2.RELEASE</version>
</dependency>
  • annotation
@FeignClient(name = "suibian",url = "${suibian.request.url}", configuration = FeignClientConfiguration.class)
  • name:指定FeignClient的名称,如果项目使用了Ribbon,name属性会作为微服务的名称,用于服务发现
  • url: url一般用于调试,可以手动指定@FeignClient调用的地址
  • decode404:当发生http 404错误时,如果该字段位true,会调用decoder进行解码,否则抛出FeignException
  • configuration: Feign配置类,可以自定义Feign的Encoder、Decoder、LogLevel、Contract
  • fallback: 定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口
  • fallbackFactory: 工厂类,用于生成fallback类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码
  • path: 定义当前FeignClient的统一前缀
  • configuration class

Spring Cloud creates a new ensemble as an ApplicationContext on demand for each named client using FeignClientsConfiguration. This contains (amongst other things) an feign.Decoder, a feign.Encoder, and a feign.Contract.

自带:FeignClientConfiguration.class,包含feign.Decoder, a feign.Encoder, and a feign.Contract

@Configuration
public class FeignFileUploadConfig {

  @Autowired
  private ObjectFactory<HttpMessageConverters> messageConverters;

  @Bean
  public Encoder feignFormEncoder() {
    return new SpringFormEncoder(new SpringEncoder(messageConverters));
  }
}
  • 添加Feign配置类,可以添加在主类下,但是不用添加@Configuration。
  • 如果添加了@Configuration而且又放在了主类之下,它将成为feign.Decoder,feign.Encoder,feign.Contract等的默认来源。那么就会所有Feign客户端实例共享,同Ribbon配置类一样父子上下文加载冲突。
  • 如果一定添加@Configuration,就放在主类加载之外的包。也可以在@ComponentScan中将其明确排除在外。
  • 建议还是不用加@Configuration。
  • 添加日志级别-代码实现 && 配置实现
//写configuration类
public class FeignConfig {
    @Bean
    public Logger.Level Logger() {
        return Logger.Level.FULL;
    }
}
//写配置
logging:
  level:
    com.xxx.xxx.FeignAPI: DEBUG #需要将FeignClient接口全路径写上# 开启日志 格式为logging.level.+Feign客户端路径
feign:
  client:
    config:
      #想要调用的微服务名称
      server-1:
        loggerLevel: FULL
  • 上传文件

调用client 被调用的项目server
client代码分为controller层 serice层 remote调用接口
server代码展示controller层

1.import
spring-cloud-starter-openfeign不支持文件上传,需要引入拓展的第三方包

 <!-- feign file upload-->
    <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.代码实现 使用@RequestPart
controller层

  @RequestMapping(value = "/addFile", method = RequestMethod.POST,consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
  public Result addFile(
      @RequestParam("id") Integer id,
      @RequestPart("file") MultipartFile multipartFile) {
    return bizSuibianService.addFile(id,multipartFile);
  }

serice层

  public Result addFile(Integer id, MultipartFile multipartFile){
    try {
      return bizSuibianFileRemote.addFile(id,multipartFile);
    } catch (Exception e) {
      e.printStackTrace();
      return Result.error("invalid param",e);
    }
  }

remote

@FeignClient(name = "suibian",url = "${suibian.request.url}",configuration = FeignFileUploadConfig.class)
public interface BizSuibianFileRemote {

@RequestMapping(value = "/addFile", method = RequestMethod.POST,consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
  MimirResult addFile(@RequestParam("id") Integer dd,
      @RequestPart("file") MultipartFile file);
}

FeignFileUploadConfig.class
放在主类之外,加了@Configuration
需要注意的是new SpringEncoder(messageConverters),这个部分的代码逻辑不清楚。个人感觉方法一是存在问题的,但事实是方法一work,方法二报错。可能跟@Configuration有关。

//方法一
@Configuration
public class FeignFileUploadConfig {

  @Autowired
  private ObjectFactory<HttpMessageConverters> messageConverters;

  @Bean
  public Encoder feignFormEncoder() {
    return new SpringFormEncoder(new SpringEncoder(messageConverters));
  }
}

//方法二
@Configuration
public class FeignFileUploadConfig {

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

Tips:该方法不支持MultipartFile[] files
因为第三方的拓展中,源码并没有对MultipartFile[] 这种情况进行处理。如果用这个类型的入参,请参考https://blog.csdn.net/qq_3295...

  • 下载文件
  • 背景:server有一个接口是返回图片的,client要调用该接口,将图片返回给前端
  • client代码分为controller层 serice层 remote调用接口

server代码展示controller层

controller

 @RequestMapping(value = "/xxxx",method = RequestMethod.GET)
  public void getImagesById(
      @RequestParam("id") Integer id,
      HttpServletResponse response){
      InputStream inputStream = null;
      OutputStream outputStream=null;
      try {
        // feign文件下载
        Response responseFromRemote = bizSuibianService.getImagesById(authCode, id);
        Response.Body body = responseFromRemote.body();
        inputStream = body.asInputStream();
        outputStream = response.getOutputStream();
        byte[] b = new byte[inputStream.available()];
        byte[] buffer = new byte[1024 * 8];
        int count = 0;
        while ((count = inputStream.read(buffer)) != -1) {
          outputStream.write(buffer, 0, count);
          outputStream.flush();
        }

      } catch (IOException e) {
        e.printStackTrace();
      } finally {
        if (inputStream != null) {
          try {
            inputStream.close();
          } catch (IOException e) {
            e.printStackTrace();
          }
        }
        if (outputStream != null) {
          try {
            response.setContentType("image/*");
            outputStream.close();
          } catch (IOException e) {
            e.printStackTrace();
          }
        }
      }
  }

先用response去接remote返回的httpServletResponse(response From Server to client),然后读取数据,写给要返回前端的httpServletResponse(response From Client to front end)

service

 @Override
  public Response getImagesById(Integer id){
    try {
      return bizSuibianRemote.getImagesById(id);
    } catch (Exception e) {
      e.printStackTrace();
      return null;
    }
  }

remote

  @RequestMapping(value = "/xxxx/{id}",method = RequestMethod.GET)
  Response getImagesById(@PathVariable("id") Integer id);
  • 注意点:remote的调用只有一个id参数;serverclient中有一个id 还有一个httpServletResposne response
  • Tips:Web服务器收到客户端的http请求,会针对每一次请求,分别创建一个用于代表请求的request对象、和代表响应的response对象。 request和response对象即然代表请求和响应,那我们要获取客户机提交过来的数据,只需要找request对象就行了。要向客户机输出数据,只需要找response对象就行了。
  • 踩过的坑:remote的调用中放了两个参数,一个id 一个response。这样处理的话,在client的controller层去拿数据的时候,会发现response.getOutPutStream()已经被调用过(这是一个只可以被调用一次的方法,mark读到数据尾,不能重置,就不能读第二次)。
  • 个人理解:用Response去接client返回的响应,就是获取这个http请求的这个resposne对象。所以不需要传response。感觉自己蠢蠢的,23333.在坑里趴了一天才出来。

server controller

   @RequestMapping(value = "/xx/{id}",method = RequestMethod.GET)
    public void getImagesById(@PathVariable("id") Integer id,
        HttpServletResponse response) {
        InputStream fis = null;
        OutputStream os = null;
        try {
            //本地图片
            ClassPathResource resource = imageService.getImagePathById(id);
            fis = resource.getInputStream();
            os = response.getOutputStream();
            int count = 0;
            byte[] buffer = new byte[1024 * 8];
            while ((count = fis.read(buffer)) != -1) {
                os.write(buffer, 0, count);
                os.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                response.setContentType("image/*");
                fis.close();
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
  • 注意点:直接用inputStream.read一个byte[]数组,不分段读的话,会出现bug。前端展示的东西是乱码的。不知道为啥,是我太蠢了,还没研究明白。

参考资料链接:

阅读 1.7k
1 声望
0 粉丝
0 条评论
1 声望
0 粉丝
宣传栏