• 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;
    }
  • 相关阅读:
    前端开发浏览器兼容问题
    pc端页面打包成安卓apk
    AJAX
    webpack
    【javascript】数据结构-集合
    【javascript】数据结构-队列
    【前端】display: box布局教程 [转]
    【前端】Flex 布局教程:语法篇 [转]
    【前端】几种实现水平垂直居中的方法总结
    【前端】jQuery选择器$()的实现原理
  • 原文地址:https://www.cnblogs.com/thinkingandworkinghard/p/16522646.html
Copyright © 2020-2023  润新知