简单使用openFeign调用第三方接口

一、使用feign调用其他服务接口

1、加入对应引用

<!-- feign 依赖 -->
<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-openfeign</artifactId>
 <version>2.2.2.RELEASE</version>
</dependency>
<dependency>
 <groupId>io.github.openfeign</groupId>
 <artifactId>feign-okhttp</artifactId>
 <version>10.7.4</version>
</dependency>
<dependency>
 <groupId>com.alipay.sdk</groupId>
 <artifactId>alipay-sdk-java</artifactId>
 <version>4.38.41.ALL</version>
</dependency>

2、在启动类开启扫描feignClient修饰的接口的注解EnableFeignClients指定扫描路径

import com.hospital.platform.common.filter.ResponseFilter;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.client.RestTemplate;

@Configuration
@SpringBootApplication(scanBasePackages = "com.hospital.platform",exclude = DataSourceAutoConfiguration.class)
@MapperScan(basePackages={"com.hospital.platform.service.*.dao"})
@EnableAsync
@EnableFeignClients(basePackages = {"com.hospital.platform.api.apaas"})
@EnableScheduling
public class HospitalApplication extends SpringBootServletInitializer {
	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
		return builder.sources(OnlineHospitalApplication.class);
	}

	public static void main(String[] args) {
		SpringApplication.run(HospitalApplication.class, args);
	}

	@Bean
	public RestTemplate restTemplate(){
		return new RestTemplate();
	}

	@Bean
	public FilterRegistrationBean someFilterRegistration()
	{
		FilterRegistrationBean registration = new FilterRegistrationBean();
		registration.setFilter(new ResponseFilter());// 配置一个返回值加密过滤器
		registration.addUrlPatterns("/*");
		registration.addInitParameter("paramName", "paramValue");
		registration.setName("responseFilter");
		return registration;
	}

}

3、编写远程调用接口

import com.api.apaas.feign.config.DataIntegrationFeignConfig;
import com.api.apaas.feign.factory.DataIntegrationClientFallbackFactory;
import com.api.regulators.constants.ServiceConstant;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;

@FeignClient(url = "http://localhost:8078/apaas",
        name = DATA-INTEGRATION-SERVICE,
        configuration = DataIntegrationFeignConfig.class,
        fallbackFactory = DataIntegrationClientFallbackFactory.class)
public interface IDataIntegrationServiceClient {

    @PostMapping(value = "/unit/test", consumes = "application/x-www-form-urlencoded;charset=UTF-8")
    String test();

}

PS:

(1)、这里@FeignClient注解的name入参指定服务名不能使用下划线连接

(2)、接口的返回值必须是指定类型的对象,使用map对象会引起类型不匹配异常

二、配置异常捕获fallback

1、先在@FeignClien注解上配置fallback属性指定异常回调的实现类

import com.api.apaas.feign.IDataIntegrationServiceClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class DataIntegrationServiceClientFallbackImpl implements IDataIntegrationServiceClient {
    private Throwable throwable;
    public Throwable getThrowable() {
        return throwable;
    }
    public void setThrowable(Throwable throwable) {
        this.throwable = throwable;
    }

    @Override
    public String test() {
        log.info("000000");
        log.error("异常捕获:{}");
        return null;
    }
}

2、在配置文件中配置开启熔断功能

#feign熔断开启
feign:
  hystrix:
    enabled: true

同时需要配置超时时间,否则请求都会进入callback

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 60000

三、对请求进行加密数据处理

注入配置类用于对请求进行加密、添加鉴权参数等处理,在@FeignClient注解的configuration属性进行配置类指定,一个@FeignClient只能指定一个

import com.common.helper.util.AESUtil;
import feign.Logger;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

@Slf4j
public class DataIntegrationFeignConfig implements RequestInterceptor {
    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }

    @Override
    public void apply(RequestTemplate template) {

        // 接口入参加密
        String jsonBody = template.requestBody().asString();
        try {
            template.body(AESUtil.encrypt(jsonBody));
        } catch (Exception e) {
            log.error("feign请求入参加密失败");
        }

        //header添加Authorization
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        template.header("Authorization",request.getHeader("accessToken"));
    }

}

四、利用okhttp拦截器对Response响应体进行日志打印和解密处理

1、首先需要把springboot版本升级到2.2.13及以上

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.2.13.RELEASE</version>
</parent>

ps:升级后如果开启多数据源出现循环引用问题,需要在启动类的@SpringBootApplication注解通过exclude属性指定多数据源配置类禁止 SpringBoot 自动注入数据源配置

2、编写对应的拦截器代码

import com.common.interceptor.FeignInterceptor;
import feign.Client;
import feign.Feign;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory;
import org.springframework.cloud.commons.httpclient.OkHttpClientFactory;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * okHttp配置
 */
@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignOkHttpConfig {

    @Resource
    private FeignInterceptor feignInterceptor;


    @Bean
    @ConditionalOnMissingBean({Client.class})
    public Client feignClient(OkHttpClient client) {
        return new feign.okhttp.OkHttpClient(client);
    }

    @Bean
    @ConditionalOnMissingBean({ConnectionPool.class})
    public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties, OkHttpClientConnectionPoolFactory connectionPoolFactory) {
        Integer maxTotalConnections = httpClientProperties.getMaxConnections();
        Long timeToLive = httpClientProperties.getTimeToLive();
        TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
        return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
    }

    @Bean
    public OkHttpClient client(OkHttpClientFactory httpClientFactory, ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) {
        Boolean followRedirects = httpClientProperties.isFollowRedirects();
        Integer connectTimeout = httpClientProperties.getConnectionTimeout();
        Boolean disableSslValidation = httpClientProperties.isDisableSslValidation();
        OkHttpClient build = httpClientFactory.createBuilder(disableSslValidation)
                .followRedirects(followRedirects)
                .connectionPool(connectionPool)
                // 连接超时
                .connectTimeout(60, TimeUnit.SECONDS)
                // 读超时
                .readTimeout(60, TimeUnit.SECONDS)
                // 写超时
                .writeTimeout(60, TimeUnit.SECONDS)
                // 是否自动重连
                .retryOnConnectionFailure(true)
                // 日志拦截器
                .addInterceptor(feignInterceptor)
                .build();
        return build;
    }
}
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import com.hospital.platform.common.helper.util.AESUtil;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import okio.Buffer;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * 拦截器
 */
@Slf4j
@Component
public class FeignInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        TimeInterval timer = DateUtil.timer();
        Response response =  chain.proceed(request);
        logForRequest(request);
        // 根据请求路径判断是否需要解密
        if (response.request().url().toString().contains("/apaas/")) {
            return decryptForResponse(response, timer);
        }
        return logForResponse(response, timer);
    }

    private Response decryptForResponse(Response response, TimeInterval timer) {
        try {
            Response.Builder builder = response.newBuilder();
            Response clone = builder.build();
            log.info("=========decrypt==start======");
            log.info(String.format("repose url:%s,code:%s,time is:%s,headers:%s", clone.request().url(), clone.code(), timer.intervalMs() + "ms", clone.protocol()));

            ResponseBody body = clone.body();
            if (body != null) {
                MediaType mediaType = body.contentType();
                if (mediaType != null && isText(mediaType)) {
                    String content = body.string();
                    content = AESUtil.decrypt(content);
                    body = ResponseBody.create(mediaType, content);
                    log.info(String.format("message:%s,contentType:%s,content is:%s,", clone.message(), mediaType, content));
                    return response.newBuilder().body(body).build();
                }
            }
        } catch (Exception e) {
            log.warn("print reponse error", e);
        }finally {
            log.info("=========decrypt==end======");

        }
        return response;
    }

    private Response logForResponse(Response response, TimeInterval timer) {
        try {
            Response.Builder builder = response.newBuilder();
            Response clone = builder.build();
            log.info("=========repose==log==start======");
            log.info(String.format("repose url:%s,code:%s,time is:%s,headers:%s", clone.request().url(), clone.code(), timer.intervalMs() + "ms", clone.protocol()));

            ResponseBody body = clone.body();
            if (body != null) {
                MediaType mediaType = body.contentType();
                if (mediaType != null && isText(mediaType)) {
                    String content = body.string();
                    log.info(String.format("message:%s,contentType:%s,content is:%s,", clone.message(), mediaType.toString(), content ));
                    body = ResponseBody.create(mediaType, content);
                    return response.newBuilder().body(body).build();
                }
            }
        } catch (Exception e) {
            log.warn("print reponse error", e);
        }finally {
            log.info("=========repose==log==end======");

        }
        return response;


    }

    private void logForRequest(Request request) {
        String url = request.url().toString();
        String method = request.method();
        Headers headers = request.headers();
        String headerStr = headers != null && headers.size() > 0 ? headers.toString() : "";
        log.info("=========request==log==start======");
        log.info(String.format("request url:%s,method:%s,headers:%s", url, method, headerStr));
        RequestBody requestBody = request.body();
        if (requestBody != null) {
            MediaType mediaType = requestBody.contentType();
            if (mediaType != null && isText(mediaType)) {
                log.info("requestBody mediaType:%s,bodyToString:%s", mediaType.toString(), bodyToString(request));
            }
        }
        log.info("=========request==log==end======");
    }

    private String bodyToString(final Request request) {
        final Request copy = request.newBuilder().build();
        final Buffer buffer=new Buffer();
        try {
            copy.body().writeTo(buffer);
        } catch (IOException e) {
            return "something error,when show requestBody";
        }
        return buffer.readUtf8();
    }


    private boolean isText(MediaType mediaType) {
        if (mediaType.type() != null && mediaType.type().equals("text")) {
            return true;
        }
        if (mediaType.subtype() != null) {
            if (mediaType.subtype().equals("json") ||
                    mediaType.subtype().equals("xml") ||
                    mediaType.subtype().equals("html") ||
                    mediaType.subtype().equals("webviewhtml")) {
                return true;
            }
        }
        return false;
    }
}

ps:这里需要注意String content = body.string();执行一次后,看源码会自动将资源关闭,再次调用string()就会空指针,所以无法多次调用body.string()

  • 21
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
OpenFeign是一个用于简化HTTP API调用的声明式Web Service客户端。在进行远程调用第三方接口时,可以通过配置来实现。 首先,需要引入feign-okhttp依赖,可以在项目的pom.xml文件中添加以下配置: ``` <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> </dependency> ``` 然后,在应用的配置文件(比如application.yml)中启用OkHttp,可以按照以下方式进行配置: ``` feign: httpclient: connection-timeout: 5000 max-connections: 1000 okhttp: enabled: true ``` 配置日志级别可以通过以下方式进行实现: ``` feign: client: config: # 全局配置 default: logger-level: BASIC # 针对某个微服务的配置 orderservice: logger-level: FULL ``` 总结起来,使用OpenFeign进行远程调用第三方接口时,可以通过引入feign-okhttp依赖来切换底层HTTP实现。然后,在配置文件中启用OkHttp并设置连接池参数。另外,还可以通过配置日志级别来记录调用过程中的日志信息。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [【SpringCloud】OpenFeign调用第三方接口API](https://blog.csdn.net/u012042111/article/details/126593267)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [Python实现基于人脸识别的上课考勤系统.zip](https://download.csdn.net/download/2302_77835532/88237252)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值