Spring cloud OpenFeign项目集成(1)

109 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第21天,点击查看活动详情

feign简化http调用,接口式声明;

  1. pom.xml, 版本在springcloud中有对应,例如: spring cloud Hoxton.SR8
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  1. springboot启动类中添加 @EnableFeignClients注解
  2. 编写远程调用接口
/** 
 * 使用FeignClient注解, name必填, url为远程调用的基础url ${}占位符,获取配置文件中的信息
 */
@FeignClient(name = "school", url = "${school.url}")
public interface SchoolClient {
    /**
      * 根据学校id批量查询学校
      *
      * @param dto
      * @return com.test.growth.school.feign.resp.BasicSchoolResp<java.util.List<com.test.growth.school.third.phone.resp.SchoolResp>>
      */
    @PostMapping("/school/getSchoolInfo")
    DefaultResp<List<SchoolResp>> getSchoolByIds(@RequestBody SchoolInfoDTO dto);
}
  1. 由于有些内部服务调用,需要在请求头中额外的信息, 如何解决这种情况?
{
  "X-Auth-appid": 2003251,
  "x-auth-sign": "4ba37068755c6ddefe56ddb",
  "x-auth-timestamp": 1617904989
}

使用Feign中Request Interceptor功能.

Request Interceptor 分为全局和部分,

  • 如何设置Request Interceptor为全局的?

    1. 自定义的Request Interceptor被spring托管,使用@Component注解
    @Component
    public class DefaultRequestInterceptor implements RequestInterceptor{}
    
    1. 自定义的自定义的Request Interceptor不被spring托管,通过配置文件配置,feign.client.config.default配置的内容都是为全局默认的.
    package com.test.growth.school.feign;
    public class DefaultRequestInterceptor implements RequestInterceptor{}
    
    feign:
      client:
        config:
          default:
          	requestInterceptors:
              - com.test.growth.school.feign.DefaultRequestInterceptor
    
  • 如何设置Request Interceptor为部分的?仅针对于部分的FeignClient

    1. 通过配置文件配置
    package com.test.growth.school.feign;
    
    /**
     * 测试拦截链路
     */
    public class OtherRequestInterceptor implements RequestInterceptor{
    }
    
    feign:
      client:
        config:
          default:
          	requestInterceptors:
              - com.test.growth.school.feign.DefaultRequestInterceptor
          school: # @FeignClient 中的name值
          	requestInterceptors:
              - com.test.growth.school.feign.DefaultRequestInterceptor  # 实际链路 default -> other
    
    1. 通过@FeignClientconfiguration属性指定配置类.

应用示例:

package com.test.growth.school.feign;

import com.test.growth.school.util.Md5Util;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;


/**
 * 负责请求头中添加
 * X-Auth-appid: appid
 * X-Auth-TimeStamp: timeMillis
 * X-Auth-Sign: sign
 */
@Slf4j
@Component
public class DefaultRequestInterceptor implements RequestInterceptor{

    @Override
    public void apply(RequestTemplate template) {
        String name = template.feignTarget().name();
        Environment env = ApplicationContextUtils.getApplicationContext().getEnvironment();
        String url = env.getProperty(name + ".url");
        String appId = env.getProperty(name + ".appId");
        String appKey = env.getProperty(name + ".appKey");
        if (StringUtils.isBlank(url) || StringUtils.isBlank(appId) || StringUtils.isBlank(appKey)) {
            log.warn("配置信息错误,请检查{}.url={}, appId={}, appKey={}等配置", name, url, appId, appKey);
            return ;
        }
        log.info("内部服务调用,请求头信息设置");
        String timeMillis = System.currentTimeMillis() / 1000 + "";
        StringBuilder content = new StringBuilder();
        content.append(appId).append("&").append(timeMillis).append(appKey);
        String sign = Md5Util.string2MD5(content.toString());
        log.info("请求头信息X-Auth-appid={}, X-Auth-TimeStamp={}, X-Auth-Sign={}", appId, timeMillis, sign);
        template.header("X-Auth-appid", appId);
        template.header("X-Auth-TimeStamp", timeMillis);
        template.header("X-Auth-Sign", sign);
    }
}
  1. 远程调用的接口具备比较统一的响应格式,例如:
@Data
public class DefaultResp<T> {
		// code = 0时操作成功
    private Integer code;

    private String msg;

    private T data;
}

比如远程调用的接口如下

@FeignClient(name = "school", url = "${school.url}")
public interface SchoolClient {
    @PostMapping("/school/getSchoolInfo")
    DefaultResp<List<SchoolResp>> getSchoolByIds(@RequestBody SchoolInfoDTO dto);
  	
    @GetMapping("/school/page")
    DefaultResp<Page<SchoolResp>> page(SchoolInfoPageDTO dto);
   
    @GetMapping("/~~")
    DefaultResp<A> a(B dto);
  
    @GetMapping("/~~")
    DefaultResp<C> c(D dto);
}

那客户端使用的时候一般写法如下

@Autowired
SchoolClient schoolClient;

public void demo1() {
  DefaultResp<A> resp = schoolClient.a(dto);
  if (resp.getCode() == 0) {
    // 调用成功
  } else {
    // 调用失败,抛出异常
  }
}