spring cloud feign (包含上傳文件和下載文件)

定位:官網摘的html

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.前端

如何使用:git

  • 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.github

自帶:FeignClientConfiguration.class,包含feign.Decoder, a feign.Encoder, and a feign.Contractweb

@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層spring

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

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

remoteide

@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。前端展現的東西是亂碼的。不知道爲啥,是我太蠢了,還沒研究明白。

參考資料連接:

相關文章
相關標籤/搜索