使用Spring Cloud OpenFeign完整构造Rest接口

引言

Spring Cloud OpenFeign 用于以声明式快速构建Restful API,它简化了与HTTP服务交互的过程,使得接口调用更加方便和简洁,是构建微服务架构的重要工具。本文将详细阐述如何构建一个健全,简洁,可靠的rest接口。

引入

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

定义接口

  • 服务间调用
@FeignClient(contextId = "intr", value = "target-service")
public interface Intr {

    @PostMapping("/api/v1/test")
    Response call(@RequestBody Request request);

}
  • 固定地址调用
@FeignClient(name = "intr", url = "http://127.0.0.1:8080")
public interface Intr {

    @PostMapping("/api/v1/test")
    Response call(@RequestBody Request request);

}
  • 动态地址调用
@FeignClient(name = "intr", url = "dynamic")
public interface Intr {

    @PostMapping("/api/v1/test")
    Response call(URI uri, @RequestBody Request request);

}

做客户端

@Service
public class Client {
    @Autowired
    private Intr intr;

    public Response doCall() {
        return intr.call(new Request());
    }
}

做服务端

@RestController
public class Server implements Intr{

    @Override
    public Response call(Request request) {
        return new Response();
    }
}

对象定义

Spring Cloud OpenFeign 支持多种类型的请求和响应对象,这些对象可以是基本类型、集合或自定义类型

  • 枚举

支持在对象中使用枚举,如果枚举值与实际值不符,则需要单独标注映射值

@Data
public class Response {
    private ResEnum code;
}

使用Jackson(默认):


public enum ResEnum {
    /**
     * 成功
     */
    succ(1),
    /**
     * 失败
     */
    fail(0);


    @JsonValue
    public final int code;
}

使用FastJson:

public enum ResEnum {
    /**
     * 成功
     */
    succ(1),
    /**
     * 失败
     */
    fail(0);


    @JSONField
    public final int code;
}
  • 时间格式

使用Jackson(默认):

public class Response {
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")
    private Date timestamp;
}

使用FastJson:

public class Response {
     @JSONField(pattern="yyyy-MM-dd HH:mm:ss")
    private Date timestamp;
}

编码/解码

openFeign默认使用Jackson,当遇到复杂对象或是使用集合包装的对象,可能会出现解码异常, 推荐改为Fastjson做为编码/解析器

    @Bean
    public ResponseEntityDecoder feignDecoder() {
        HttpMessageConverter<?> fastJsonConverter = createFastJsonConverter();
        ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(fastJsonConverter);
        return new ResponseEntityDecoder(new SpringDecoder(objectFactory));
    }

    @Bean
    public SpringEncoder feignEncoder() {
        HttpMessageConverter<?> fastJsonConverter = createFastJsonConverter();
        ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(fastJsonConverter);
        return new SpringEncoder(objectFactory);
    }

    private HttpMessageConverter<?> createFastJsonConverter() {
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        List<MediaType> supportedMediaTypes = new ArrayList<>();
        supportedMediaTypes.add(MediaType.APPLICATION_JSON);
        fastConverter.setSupportedMediaTypes(supportedMediaTypes);
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(
                SerializerFeature.DisableCircularReferenceDetect,
                SerializerFeature.WriteMapNullValue
        );
        fastConverter.setFastJsonConfig(fastJsonConfig);
        return fastConverter;
    }

异常处理

  • 客户端

指定接口:

public class MyErrorDecoder extends ErrorDecoder.Default {

    @Override
    public Exception decode(String methodKey, Response response) {
        Exception exception = super.decode(methodKey, response);
        switch (response.status()) {
            case 401:
                return new IllegalStateException("鉴权认证失败", exception);
            case 404:
                return new IllegalStateException("地址错误", exception);
            case 503:
                return new IllegalStateException("服务不可用", exception);
            default:
                return new IllegalStateException("请求失败", exception);
        }
    }
}
@FeignClient(contextId = "intr", value = "target-service" ,  configuration = MyErrorDecoder.class)
public interface Intr {

    @PostMapping("/api/v1/test")
    Response call(@RequestBody Request request);

}

全局定义:

    @Bean
    public ErrorDecoder myErrorDecoder() {
        return (key, response) -> {
            switch (response.status()) {
                case 401:
                    return new IllegalAccessException("鉴权认证失败", exception);
                case 404:
                    return new IllegalArgumentException("地址错误", exception);
                default:
                    return new RuntimeException("请求失败", exception);
            }
        };
    }
  • 服务端
@RestControllerAdvice
public class ErrorHandler {

    @ExceptionHandler(IllegalAccessException.class)
    public ResponseEntity<?> handleAccessException(IllegalAccessException exception) {
        return ResponseEntity.status(401).build();
    }
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<?> handleArgumentException(IllegalArgumentException exception) {
        return ResponseEntity.status(404).build();
    }
    

}

HTTPS

  • 单向认证

客户端:

@Value("${server.ssl.trust-store}")
private String trustStorePath;

@Value("${server.ssl.trust-store-password}")
private String trustStorePassword;

@Bean
public SSLContext sslContext() throws Exception {
  return SSLContextBuilder.
    create().
    loadTrustMaterial(ResourceUtils.getFile(trustStorePath), trustStorePassword.toCharArray()).
    build();
}

@Bean
public Client feignClient(SSLContext sslContext) {
  return new Client.Default(sslContext.getSocketFactory(), (hostname, session) -> true);
}
#application.yml
server:
  ssl:
    enabled: false
    trust-store: classpath:truststore.jks
    trust-store-type: JKS
    trust-store-password: 123456  

服务端:

#application.yml
server:
  ssl:
    key-store: classpath:keystore.jks
    key-store-type: JKS
    key-store-password: 123456  
  • 双向认证

客户端/服务端:

Feign配置同单向认证

//application.yml
server:
  ssl:
    key-store: classpath:keystore.jks
    key-store-type: JKS
    key-store-password: 123456
    trust-store: classpath:truststore.jks
    trust-store-type: JKS
    trust-store-password: 123456 

绕过认证:

    @Bean
    public Client feignClient() throws NoSuchAlgorithmException, KeyManagementException {
        SSLContext ctx = SSLContext.getInstance("SSL");
        X509TrustManager tm = new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) {
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };
        ctx.init(null, new TrustManager[]{tm}, null);
        return new Client.Default(ctx.getSocketFactory(), (hostname, session) -> true);
    }

日志级别

  • 内部配置
@Configuration
public class FeignConfiguration {
 
    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.BASIC;
    }
}

  • 外部配置
#application.yml
feign:
  client:
    config:
      default:
        loggerLevel: basic

超时

  • connectTimeout:指客户端连接服务端的超时时间
  • readTimeout:指客户端连接完成后,等待服务端响应结果的超时时间
feign:
  client:
    config:
      default:
        connectTimeout: 10000
        readTimeout: 10000

开启压缩

feign:
  compression:
    request:
      enabled: true 
      mime-types: text/xml,application/xml, application/json 
      min-request-size: 1024 
    response:
      enabled: true

okhttp3

OkHttp3 是一个 HTTP 客户端库,它可以更高效地处理网络请求

  • 导入
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>
  • 开启
feign:
  okhttp:
    enabled: true
  • 自定义
    @Bean
     public OkHttpClient feignClient() {
        return new OkHttpClient.Builder()
                //设置连接超时
                .connectTimeout(10, TimeUnit.SECONDS)
                //设置读超时
                .readTimeout(10, TimeUnit.SECONDS)
                //设置写超时
                .writeTimeout(10, TimeUnit.SECONDS)
                //是否自动重连
                .retryOnConnectionFailure(true)
                //设置连接池
                .connectionPool(new ConnectionPool(10, 5L, TimeUnit.MINUTES))
                .build();
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值