feign调用支持三方接口改造记录

       平时项目里面的feign都是用在自己的微服务中的,但是有时候需要调用三方的接口。之前项目是用的Retrofit封装的,但是如果超时或者异常了,日志没有打印出来,我们也获取不到返回结果。这两天就整理了下feign开启三方调用。

       因为feign是用了okhttpclient的,只是默认配置没有开启日志,以及没有使用okhttpclient.所以调用三方接口需要额外配置。

        先是去git上下载了feign的源码,大概看下了。里面好多看的不是很懂,不过从一些常用的@Configuration、@ConditionalOnClass大概也能猜到。然后去网上看了下别人已经写好的feign源码解读(参考:https://zhuanlan.zhihu.com/p/526427027),之后就开始着手思考怎么替换掉项目的retrofit。

      网上继续搜索,发现别人已经实现好了的。好的 ,按照网上的思路来吧。参考:https://www.zhangshengrong.com/p/AvN6Y8dWam/。其实就是自己实现okhttpclient的配置,因为springboot的默认装配机制,feign已经把okhttpclient装配进去了,而且用的是@ConditionalOnMissingBean({okhttp3.OkHttpClient.class})注解,只会注册一次。导致后面重新注册时候注册不进去。因为思路就是自己重写okhttp,不使用自动装配的。之后使用@AutoConfigureAfter(FeignAutoConfiguration.class) 。自己实现了的代码如下:

    

package com.gwm.marketing.restfulfeign;


import feign.Feign;
import feign.Logger;
import feign.RequestInterceptor;
import okhttp3.ConnectionPool;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
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 java.util.concurrent.TimeUnit;

/**
 * @author fanht
 * @descrpiton 使用配置类声明http接口
 * @AutoConfigureBefore(FeignAutoConfiguration.class) 不使用默认的自动配置,手动实现okhttpclient配置到spring容器
 * @date 2022/7/25 16:00:30
 * @versio 1.0
 */
@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureAfter(FeignAutoConfiguration.class)
public class OraFeignConfig {

    private static final int DEFAULT_TIMEOUT = 10;


    /**
     * 注入自定义okHttpClient
     * @return
     */
    @Bean
    public okhttp3.OkHttpClient okHttpClient(){
        return new okhttp3.OkHttpClient().newBuilder().
                readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS).
                connectTimeout(DEFAULT_TIMEOUT,TimeUnit.SECONDS).
                writeTimeout(DEFAULT_TIMEOUT,TimeUnit.SECONDS).
                connectionPool(new ConnectionPool()).build();
    }

    @Bean
    public RequestInterceptor requestInterceptor(){
        return new OraFeignRequestIntercepter();
    }
    @Bean
    public Interceptor oraInterceptor(){
        return new OraClientAlermIntercepter();
    }

    @Bean
    public Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }

    @Bean
    public Logger logger(){
        return new OraFeignLogger();
    }


    @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);
    }

    /**
     * 自定义请求日志拦截器
     * @param httpClientFactory
     * @param connectionPool
     * @param httpClientProperties
     * @return
     */
    @Bean
    public OkHttpClient client(OkHttpClientFactory httpClientFactory, ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) {
        Boolean followRedirects = httpClientProperties.isFollowRedirects();
        Integer connectTimeout = httpClientProperties.getConnectionTimeout();
        Boolean disableSslValidation = httpClientProperties.isDisableSslValidation();
        return httpClientFactory.createBuilder(disableSslValidation)
                .connectTimeout((long)connectTimeout, TimeUnit.MILLISECONDS)
                .followRedirects(followRedirects)
                .connectionPool(connectionPool)
                .addInterceptor( new OraClientAlermIntercepter())
                .build();
    }


}

      先按照作者的思路,写出来后本地要调试。因为考虑到效果和之前的retrofit效果是一样的,于是就用同样的接口在本地测试,看看和之前的返回结果是不是一样的。嗯,又有新问题了:feign怎么支持传多个header 以及传对象?

   参考了下这篇介绍:https://blog.csdn.net/hkk666123/article/details/113964715

   最终用了@RequestHeader 和 @RequestBody达到了和之前一样的效果。

    

package com.gwm.marketing.restfulfeign.sso;



import com.gwm.marketing.common.vo.beantech.BeanTechResponse;
import com.gwm.marketing.common.vo.beantech.BeanTechUserInfo;
import com.gwm.marketing.dto.beantech.LoginAccountDto;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;

import java.util.Map;

/**
 * SSO接口调用-通过feign的方式来远程调用 测试使用
 * @author fanht
 * @version V1.0.0
 * @description
 * @date 2022-07-26
 **/

@FeignClient(name = "feignSsoLoginTest",url = "${gwm.sso.ssoUrl}")
public interface OraFeignSsoLoginClient {

    /**
     * 通过feign调用仙豆RPC
     * @param headerMap
     * @param loginAccountDto
     * @return
     */
    @PostMapping(value = "/app-api/api/v1.0/userAuth/loginWithSMS")
    BeanTechResponse<BeanTechUserInfo> loginAccountWithSms(@RequestHeader Map<String, String> headerMap, @RequestBody LoginAccountDto loginAccountDto);
}

 

  继续调试,又碰到了新的问题:调试通过后,发现在本地debug模式下,总是会概率出现这个问题:

  

Stream is closed

网上搜了下原因,说是在debug模式下 ,toString默认会关闭流,而我自己是重写了feign的logger日志,读取流时候用的是feign的关闭流的方式。那种方式会关闭所有的流,用apache的common包的io流关闭解决了。参考: https://www.it1352.com/150589.html

代码如下:

 

package com.gwm.marketing.restfulfeign;

import com.alibaba.fastjson.JSONObject;
import com.gwm.marketing.restfulfeign.alerm.OraRpcDingdingConfiguration;
import feign.Logger;
import feign.Request;
import feign.Response;
import org.apache.commons.io.IOUtils;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
import java.io.IOException;
import java.text.MessageFormat;

/**
 * @author fanht
 * @descrpiton
 * @date 2022/7/26 14:09:39
 * @versio 1.0
 */
@Configuration
public class OraFeignLogger extends Logger {

    private static final int PASS_STATUS= 200;
    @Resource
    private OraRpcDingdingConfiguration oraRpcDingdingConfiguration;

    @Override
    protected Response logAndRebufferResponse(String configKey,
                                              Level logLevel,
                                              feign.Response response,
                                              long elapsedTime) throws IOException {
        System.out.println("===========远程RPC调用耗时============" + elapsedTime);
        int status = response.status();
        if (elapsedTime > oraRpcDingdingConfiguration.getRpc().getRpcTimeOut()) {
            Request request = response.request();
            String requestMsg = request.httpMethod().name() + " " + request.url() + " HTTP/1.1";
            String bodyMsg = request.body() != null ? new String(request.body()): "";
            byte[] bodyData = null;
            String message = "";
            if(response.body() != null){
                try {
                    bodyData = IOUtils.toByteArray(response.body().asInputStream());
                } catch (IOException e) {
                    e.printStackTrace();
                }
                message = MessageFormat.format("调用三方接口耗时告警:项目:{0},环境:{1},IP:{2},接口请求方式以及请求地址:{3},耗时时间:{4}毫秒,请求入参:{5},三方返回信息:{6}",
                        OraRpcDingdingConfiguration.applicationName, OraRpcDingdingConfiguration.env, OraIpUtil.initIp() , requestMsg, elapsedTime, bodyMsg,"");
             } else {
                message = MessageFormat.format("调用三方接口耗时告警:项目:{0},环境:{1},IP:{2},接口请求方式以及请求地址:{3},耗时时间:{4}毫秒,请求入参:{5}",
                        OraRpcDingdingConfiguration.applicationName, OraRpcDingdingConfiguration.env, OraIpUtil.initIp() , requestMsg, elapsedTime,bodyMsg);
            }
            sendDingdingAlerm(message,
                    oraRpcDingdingConfiguration.getToken().getUrl());
        }else if(PASS_STATUS != status){
            Request request = response.request();
            String requestMsg = request.httpMethod().name() + " " + request.url() + " HTTP/1.1";
            String bodyMsg = request.body() != null ? new String(request.body()): "";
            byte[] bodyData = null;
            String message = "";
            if(response.body() != null){
                bodyData = IOUtils.toByteArray(response.body().asInputStream());
                message = MessageFormat.format("调用三方接口异常告警:项目:{0},环境:{1},IP:{2},接口请求方式以及请求地址:{3},耗时时间:{4}毫秒,请求入参:{5},三方返回信息:{6}",
                        OraRpcDingdingConfiguration.applicationName, OraRpcDingdingConfiguration.env, OraIpUtil.initIp() , requestMsg, elapsedTime, bodyMsg,response.toBuilder().body(bodyData).build());
            } else {
                message = MessageFormat.format("调用三方接口异常告警:项目:{0},环境:{1},IP:{2},接口请求方式以及请求地址:{3},耗时时间:{4}毫秒,请求入参:{5}",
                        OraRpcDingdingConfiguration.applicationName, OraRpcDingdingConfiguration.env, OraIpUtil.initIp() , requestMsg, elapsedTime,bodyMsg);
            }
            sendDingdingAlerm(message,
                    oraRpcDingdingConfiguration.getToken().getUrl());

        }
        log(configKey, "logLevel【%s】, body【%s】, response【%s】", JSONObject.toJSON(logLevel),
                JSONObject.toJSON(response), elapsedTime);
        return response;
    }

    @Override
    protected void log(String configKey, String format, Object... args) {
    }

    /**
     * 发送钉钉消息
     */
    public static void sendDingdingAlerm(String message, String dingdingTokenUrl) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("msgtype", "text");
        JSONObject content = new JSONObject();
        content.put("content", message);
        jsonObject.put("text", content);
        OraHttpClient oraHttpClient = new OraHttpClient();
        String response = null;
        try {
            String[] dingdingArr = dingdingTokenUrl.split(",");
            if (dingdingArr != null && dingdingArr.length > 0) {
                for (int i = 0; i < dingdingArr.length; i++) {
                    response = oraHttpClient.post(dingdingArr[i], jsonObject.toJSONString());
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


}

 

最后一个问题就是feign默认开启日志的问题,直接设置解决吧。

 

@Bean
public Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
posted @ 2022-07-26 21:01  Doyourself!  阅读(1055)  评论(0编辑  收藏  举报