All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.taotao.cloud.openfeign.configuration.FeignAutoConfiguration Maven / Gradle / Ivy

There is a newer version: 2025.01
Show newest version
/*
 * Copyright (c) 2020-2030, Shuigedeng ([email protected] & https://blog.taotaocloud.top/).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.taotao.cloud.openfeign.configuration;

import com.taotao.cloud.common.constant.StarterName;
import com.taotao.cloud.common.utils.log.LogUtils;
import com.taotao.cloud.openfeign.aspect.FeignRetryAspect;
import com.taotao.cloud.openfeign.endpoint.FeignClientEndpoint;
import com.taotao.cloud.openfeign.feign.FeignErrorDecoder;
import com.taotao.cloud.openfeign.feign.FeignInnerContract;
import com.taotao.cloud.openfeign.feign.FeignRequestInterceptor;
import com.taotao.cloud.openfeign.feign.FeignResponseEntityDecoder;
import com.taotao.cloud.openfeign.formatter.DateFormatRegister;
import com.taotao.cloud.openfeign.properties.FeignProperties;
import com.taotao.cloud.openfeign.properties.LoadbalancerProperties;
import feign.*;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.form.spring.SpringFormEncoder;
import feign.optionals.OptionalDecoder;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.support.HttpMessageConverterCustomizer;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;

/**
 * Feign 自动配置
 *
 * @author shuigedeng
 * @version 2023.07
 * @since 2023-07-06 13:42:21
 */
@EnableFeignClients(basePackages = {"com.taotao.cloud.*.api.feign"})
// @AutoConfiguration(before = SentinelFeignAutoConfiguration.class)
@EnableConfigurationProperties({LoadbalancerProperties.class, FeignProperties.class})
@ConditionalOnProperty(prefix = FeignProperties.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
public class FeignAutoConfiguration implements InitializingBean {

    @Override
    public void afterPropertiesSet() throws Exception {
        LogUtils.started(FeignAutoConfiguration.class, StarterName.OPENFEIGN_STARTER);
    }

    @Bean
    @ConditionalOnMissingBean
    public Contract contract() {
        return new FeignInnerContract();
    }

    /**
     * Feign 客户端的日志记录,默认级别为NONE Logger.Level 的具体级别如下: NONE:不记录任何信息 BASIC:仅记录请求方法、URL以及响应状态码和执行时间
     * HEADERS:除了记录 BASIC级别的信息外,还会记录请求和响应的头信息 FULL:记录所有请求与响应的明细,包括头信息、请求体、元数据
     */
    @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }

    // @Bean
    // @Profile({"dev", "test"})
    // public Logger.Level devFeignLoggerLevel() {
    //	return Logger.Level.FULL;
    // }
    // @Bean
    // @Profile({"docker", "pre", "prod"})
    // public Logger.Level prodFeignLoggerLevel() {
    //	return Logger.Level.BASIC;
    // }

    @Bean
    public DateFormatRegister dateFormatRegister() {
        return new DateFormatRegister();
    }

    /**
     * 使用feign默认的重试机制
     */
    @Bean
    public Retryer retryer() {
        return new Retryer.Default();
    }

    /**
     * 使用自定义注解重试机制
     */
    @Bean
    public FeignRetryAspect feignRetryAspect() {
        return new FeignRetryAspect();
    }

    /**
     * Feign支持文件上传
     */
    @Bean
    @Primary
    @Scope("prototype")
    public Encoder multipartFormEncoder(ObjectFactory messageConverters) {
        return new SpringFormEncoder(new SpringEncoder(messageConverters));
    }

    // @Bean
    // public Encoder feignEncoder() {
    //	return new SpringEncoder(feignHttpMessageConverter());
    // }
    // @Bean("starterFeignDecoder")
    // public Decoder feignDecoder(ObjectProvider customizers) {
    //	return new OptionalDecoder(
    //		new ResultDecode(new SpringDecoder(feignHttpMessageConverter(), customizers)));
    // }
    @Bean
    public Decoder feignDecoder(
            ObjectFactory messageConverters,
            ObjectProvider customizers) {
        return new OptionalDecoder(new FeignResponseEntityDecoder(new SpringDecoder(messageConverters, customizers)));
    }

    @Bean
    public FeignErrorDecoder feignErrorDecoder() {
        return new FeignErrorDecoder();
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnAvailableEndpoint
    public FeignClientEndpoint feignClientEndpoint(ApplicationContext context) {
        return new FeignClientEndpoint(context);
    }

    /**
     * 使用feign client访问别的微服务时,将上游传过来的access_token、username、roles等信息放入header传递给下一个服务
     */
    @Bean
    @ConditionalOnMissingBean(FeignRequestInterceptor.class)
    public RequestInterceptor feignRequestInterceptor() {
        LogUtils.trace(" Bean [Feign Request Interceptor] Auto Configure.");
        return new FeignRequestInterceptor();
    }

    /**
     * FeignClient超时设置
     * feign超时设置有3种方式:配置文件直接配置FeignClient、自定义Request.Options及配置文件配置Ribbon,优先级从高到低如下。
     * 1、配置文件里对特定FeignClient配置属性: feign.client.config.demo.connectTimeout=1000,feign.client.config.demo.readTimeout=2000 ;
     * 2、自定义对特定FeignClient生效的Request.Options类型的Bean;
     * 3、配置文件里对所有FeienClient属性的配置:feign.client.config.default.connectTimeout=1000,feign.client.config.default.readTimeout=5000
     * 4、对全体FeignClient生效的Request.Options类型的Bean;
     * 5、特定服务的ribbon配置:demo.ribbon.ConnectTimeout=1000,demo.ribbon.ReadTimeout=5000
     * 6、全体服务的ribbon配置:ribbon.ConnectTimeout=1000,ribbon.ReadTimeout=5000
     * 7、Ribbon默认配置:默认连接超时和读取超时都是1000,即1秒
     * 

* 总结一下: * 1、FeignClient的直接配置高于Ribbon的配置 * 2、特定服务的配置高于全体服务的配置 * 3、配置文件的配置高于自定义Request.Options * 4、如果有特定服务的Options和全体服务的配置文件配置,遵循第二条规则,以特定服务的Options为准; * 5、如果有特性服务的Ribbon配置和全体服务的FeignClient配置,遵循第一条规则,以FeingClient的配置为准 *

* 最佳实践: * 1、不要采用Ribbon配置而要直接配置FeignClient,即配置feign.client.xx * 2、配置文件配置全体FeignClient的超时设置,同时对特定服务有特殊设置的,也在配置文件里配置 *

* * @see */ @Bean public Request.Options options() { return new Request.Options(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true); } /** 设置解码器为fastjson */ // private ObjectFactory feignHttpMessageConverter() { // final HttpMessageConverters httpMessageConverters = new HttpMessageConverters( // this.getFastJsonConverter()); // return () -> httpMessageConverters; // } // // private FastJsonHttpMessageConverter getFastJsonConverter() { // FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter(); // // List supportedMediaTypes = new ArrayList<>(); // MediaType mediaTypeJson = MediaType.valueOf(MediaType.APPLICATION_JSON_VALUE); // supportedMediaTypes.add(mediaTypeJson); // converter.setSupportedMediaTypes(supportedMediaTypes); // FastJsonConfig config = new FastJsonConfig(); // config.getSerializeConfig().put(JSON.class, new SwaggerJsonSerializer()); // config.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect); // converter.setFastJsonConfig(config); // // return converter; // } /// ** // * RestTemplate 相关的配置 // */ // @Configuration // @ConditionalOnClass(okhttp3.OkHttpClient.class) // public static class RestTemplateConfiguration implements InitializingBean { // @Override // public void afterPropertiesSet() throws Exception { // LogUtil.started(RestTemplateConfiguration.class, StarterName.FEIGN_STARTER); // } // // private static final Charset UTF_8 = StandardCharsets.UTF_8; // private final ObjectMapper objectMapper = JsonUtil.MAPPER; // // @Bean // @ConditionalOnMissingBean(FeignLoggerFactory.class) // public FeignLoggerFactory getInfoFeignLoggerFactory() { // return new InfoFeignLoggerFactory(); // } // // // /** // * 配置OkHttpClient // * // * @param httpClientFactory httpClient 工厂 // * @param connectionPool 链接池配置 // * @param httpClientProperties httpClient配置 // * @return OkHttpClient // */ // @Bean // @ConditionalOnMissingBean(okhttp3.OkHttpClient.class) // public okhttp3.OkHttpClient okHttp3Client( // OkHttpClientFactory httpClientFactory, // okhttp3.ConnectionPool connectionPool, // FeignHttpClientProperties httpClientProperties) { // return httpClientFactory.createBuilder(httpClientProperties.isDisableSslValidation()) // .followRedirects(httpClientProperties.isFollowRedirects()) // .writeTimeout(Duration.ofSeconds(30)) // .readTimeout(Duration.ofSeconds(30)) // .connectTimeout(Duration.ofMillis(httpClientProperties.getConnectionTimeout())) // .connectionPool(connectionPool) // .build(); // } // // /** // * okhttp3 链接池配置 // * // * @param connectionPoolFactory 链接池配置 // * @param hcp httpClient配置 // * @return okhttp3.ConnectionPool // */ // @Bean // @ConditionalOnMissingBean(okhttp3.ConnectionPool.class) // public okhttp3.ConnectionPool okHttp3ConnectionPool(FeignHttpClientProperties hcp, // OkHttpClientConnectionPoolFactory connectionPoolFactory) { // return connectionPoolFactory.create(hcp.getMaxConnections(), hcp.getTimeToLive(), // hcp.getTimeToLiveUnit()); // } // // /** // * 解决 RestTemplate 传递Request header // */ // @Bean // public RestTemplateHeaderInterceptor requestHeaderInterceptor() { // return new RestTemplateHeaderInterceptor(); // } // // /** // * 支持负载均衡的 LbRestTemplate, 传递请求头,一般用于内部 http 调用 // * // * @param httpClient OkHttpClient // * @param interceptor RestTemplateHeaderInterceptor // * @return LbRestTemplate // */ // @Bean("lbRestTemplate") // @LoadBalanced // @SentinelRestTemplate // @ConditionalOnMissingBean(RestTemplate.class) // public RestTemplate lbRestTemplate(okhttp3.OkHttpClient httpClient, // RestTemplateHeaderInterceptor interceptor) { // RestTemplate lbRestTemplate = new RestTemplate( // new OkHttp3ClientHttpRequestFactory(httpClient)); // lbRestTemplate.setInterceptors(Collections.singletonList(interceptor)); // this.configMessageConverters(lbRestTemplate.getMessageConverters()); // return lbRestTemplate; // } // // /** // * 普通的 RestTemplate,不透传请求头,一般只做外部 http 调用 // * // * @param httpClient OkHttpClient // * @return RestTemplate // */ // @Bean // @SentinelRestTemplate // public RestTemplate restTemplate(okhttp3.OkHttpClient httpClient) { // RestTemplate restTemplate = new RestTemplate( // new OkHttp3ClientHttpRequestFactory(httpClient)); // this.configMessageConverters(restTemplate.getMessageConverters()); // return restTemplate; // } // // private void configMessageConverters(List> converters) { // converters.removeIf(c -> c instanceof StringHttpMessageConverter // || c instanceof MappingJackson2HttpMessageConverter); // converters.add(new StringHttpMessageConverter(UTF_8)); // converters.add(new MappingJackson2HttpMessageConverter(this.objectMapper)); // } // } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy