fegin扩展优化

young 534 2021-11-01

支持文件传输

依赖 https://github.com/OpenFeign/feign-form
spring-cloud-starter-openfeign里包含。
示例代码:

@Configuration
public class MultipartSupportConfiguration {
 
   @Autowired
   private ObjectFactory<HttpMessageConverters> messageConverters;
 
   /**
    * 支持form表单
    */
   @Bean
   public Encoder feignFormEncoder() {
      return new SpringFormEncoder(new SpringEncoder(messageConverters));
   }
 
   /**
    * FieldQueryMapEncoder 替换为 BeanQueryMapEncoder,解析父类属性
    */
   @Bean
   public Feign.Builder feignBuilder() {
      return Feign.builder()
            .queryMapEncoder(new BeanQueryMapEncoder())
            .retryer(Retryer.NEVER_RETRY);
   }
 
}
 
//--------------------------------------------------
feign client 接口定义:
@PostMapping(value = "/file/upload/single", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
ApiResponse<FileUploadResponse> uploadSingle(@RequestPart("file") MultipartFile file, @SpringQueryMap FileUploadRequest fileUploadRequest);
 
//--------------------------------------------
使用自定义配置
@FeignClient(value = "credit-file-service", path = "credit-file-service",
      configuration = {MultipartSupportConfiguration.class, FeignLoggingConfiguration.class})
public interface CreditFileApi {}

fegin log

@Configuration
public class FeignLoggingConfiguration {

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

    @Bean
    Logger customFeignInfoLogger() {
        return new CustomFeignInfoLogger();
    }
}

@Slf4j
public class CustomFeignInfoLogger extends Slf4jLogger {

    @Override
    protected void logRequest(String configKey, Level logLevel, Request request) {
        FeignRequest feignRequest = new FeignRequest();
        feignRequest.setMethod(request.method());
        feignRequest.setUrl(request.url());

        if (logLevel.ordinal() >= Level.HEADERS.ordinal()) {
            for (String field : request.headers().keySet()) {
                for (String value : valuesOrEmpty(request.headers(), field)) {
                    feignRequest.addHeader(field, value);
                }
            }
            int bodyLength = 0;
            if (request.body() != null) {
                bodyLength = request.body().length;
                if (logLevel.ordinal() >= Level.FULL.ordinal()) {
                    feignRequest.setBody(request.charset() != null ? new String(request.body(), request.charset()) : "Binary data");
                }
            }
            feignRequest.setBodyLength(bodyLength);
        }
        log.info("{} == >> feign Request : {} ", configKey, feignRequest);
    }

    @Override
    protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response, long elapsedTime) throws IOException {
        FeignResponse feignResponse = new FeignResponse();
        int status = response.status();
        feignResponse.setStatus(response.status());
        feignResponse.setReason(response.reason() != null && logLevel.compareTo(Level.NONE) > 0 ? " " + response.reason() : "");
        feignResponse.setTimeTaken(elapsedTime);

        if (logLevel.ordinal() >= Level.HEADERS.ordinal()) {
            for (String field : response.headers().keySet()) {
                for (String value : valuesOrEmpty(response.headers(), field)) {
                    feignResponse.addHeader(field, value);
                }
            }

            if (response.body() != null && !(status == 204 || status == 205)) {
                byte[] bodyData = Util.toByteArray(response.body().asInputStream());
                if (logLevel.ordinal() >= Level.FULL.ordinal() && bodyData.length > 0) {
                    feignResponse.setBody(Util.decodeOrDefault(bodyData, UTF_8, "Binary data"));
                }
                log.info("{} << == feign Response : {} ", configKey, feignResponse);
                return response.toBuilder().body(bodyData).build();
            } else {
                log.info("{} << == feign Response : {} ", configKey, feignResponse);
            }
        }
        return response;
    }

    @Setter
    private class FeignResponse {
        private int status;
        private String reason;
        private long timeTaken;
        private List<String> headers;
        private String body;

        public void addHeader(String key, String value) {
            if (headers == null) {
                headers = new ArrayList<>();
            }
            headers.add(String.format("%s: %s", key, value));
        }

        @Override
        public String toString() {
            return String.format("Status = %s, Reason = %s, TimeTaken = %s, Headers = %s Body = %s, BodyLength = %s Bytes",
                    status, reason, timeTaken, headers, body, (body != null && body.trim().length() > 0 ? body.length() : 0));
        }
    }

    @Setter
    private class FeignRequest {
        private String method;
        private String url;
        private List<String> headers;
        private String body;
        private int bodyLength;

        public void addHeader(String key, String value) {
            if (headers == null) {
                headers = new ArrayList<>();
            }
            headers.add(String.format("%s: %s", key, value));
        }

        @Override
        public String toString() {
            return String.format("Method = %s, url = %s, Headers = %s Body = %s, BodyLength = %s Bytes",
                    method, url, headers, body, bodyLength);
        }
    }
}

使用httpclient线程池

依赖

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>

配置

application.yaml

feign:
  httpclient:
    enabled: true