package com.vci.starter.web.autoconfigure; import com.alibaba.fastjson.JSONObject; import com.vci.starter.web.constant.TokenKeyConstant; import com.vci.starter.web.pagemodel.SessionInfo; import com.vci.starter.web.util.VciBaseUtil; import feign.*; import feign.form.spring.SpringFormEncoder; import feign.okhttp.OkHttpClient; import okhttp3.ConnectionPool; import org.slf4j.MDC; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory; import org.springframework.cloud.commons.httpclient.OkHttpClientFactory; import org.springframework.cloud.openfeign.support.FeignHttpClientProperties; import org.springframework.cloud.openfeign.support.ResponseEntityDecoder; import org.springframework.cloud.openfeign.support.SpringDecoder; import org.springframework.cloud.openfeign.support.SpringEncoder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.web.bind.annotation.RequestMethod; import javax.annotation.PreDestroy; import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.concurrent.TimeUnit; /** * feign客户端配置 * @author weidy * @date 2019/11/5 10:23 AM */ @AutoConfigureAfter(AppAutoConfigure.class) @Configuration public class FeignConfig implements RequestInterceptor { /** * 使用okHttpClient来传输数据 */ private okhttp3.OkHttpClient okHttpClient; /** * 当前应用程序的私钥 */ @Value("${app.privateTokenKey:}") private String privateTokenKey; /** * 客户端连接池 * @param httpClientProperties http链接配置 * @param connectionPoolFactory 连接池工程 * @return 连接池 */ @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); } /** * 初始化okHttp的客户端对象 * @param httpClientFactory 客户端链接工程 * @param connectionPool 连接池 * @param httpClientProperties 客户端链接配置 * @return */ @Bean public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory, ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) { Boolean followRedirects = httpClientProperties.isFollowRedirects(); Integer connectTimeout = httpClientProperties.getConnectionTimeout(); Boolean disableSslValidation = httpClientProperties.isDisableSslValidation(); this.okHttpClient = httpClientFactory.createBuilder(disableSslValidation).connectTimeout((long)connectTimeout, TimeUnit.MILLISECONDS).followRedirects(followRedirects).readTimeout(30*60,TimeUnit.SECONDS).connectionPool(connectionPool).build(); return this.okHttpClient; } /** * 销毁之前关闭okHttp */ @PreDestroy public void destroy() { if (this.okHttpClient != null) { this.okHttpClient.dispatcher().executorService().shutdown(); this.okHttpClient.connectionPool().evictAll(); } } /** * 配置feign的客户端类 * @param client 客户端链接对象 * @return 客户端连接对象 */ @Bean @ConditionalOnMissingBean({Client.class}) public Client feignClient(okhttp3.OkHttpClient client) { return new OkHttpClient(client); } /** * 设置请求的连接时间和读取的时间 * @param client 链接的客户端 * @return 链接的客户端 */ @Bean public Request.Options feignOptions(okhttp3.OkHttpClient client) { return new Request.Options(client.connectTimeoutMillis(), client.readTimeoutMillis()); } /** * 配置feign的日志等级 * @return 日志等级 */ @Bean Logger.Level feignLoggerLevel() { //这里记录所有,根据实际情况选择合适的日志level return Logger.Level.FULL; } /** * 将fastjson设置到decoder中,解码器 * @return decoder */ @Bean public ResponseEntityDecoder feignDecoder(){ HttpMessageConverter fastJsonConverter = SpringMVCConfig.createFastJsonConverter(); ObjectFactory objectFactory = () -> new HttpMessageConverters(new StringHttpMessageConverter(StandardCharsets.UTF_8),fastJsonConverter); return new ResponseEntityDecoder(new SpringDecoder(objectFactory)); } /** * 将fastjson设置到encoder中,编码器 * @return encoder */ @Bean public SpringFormEncoder feignEncoder(){ HttpMessageConverter fastJsonConverter = SpringMVCConfig.createFastJsonConverter(); ObjectFactory objectFactory = () -> new HttpMessageConverters(new StringHttpMessageConverter(StandardCharsets.UTF_8),fastJsonConverter); return new SpringFormEncoder(new SpringEncoder(objectFactory)); } /** * 在发送请求的时候,设置拦截器,将用户的token和当前服务的私钥传递过去,并且传递当前服务的名称用于追溯日志 * @param requestTemplate 请求的目标 */ @Override public void apply(RequestTemplate requestTemplate) { //如果是GET,但是body里有内容 Request.Body body = requestTemplate.requestBody(); if(RequestMethod.GET.toString().equalsIgnoreCase(requestTemplate.method()) && body != null && body.length() > 0){ //主要是列表查询和树查询这几个地方,支持map的传输 String bodyString = new String(body.asBytes(),requestTemplate.requestCharset()); try{ JSONObject jsonObject = JSONObject.parseObject(bodyString); for(String key : jsonObject.keySet()){ Object value = jsonObject.get(key); if(value == null){ requestTemplate.query(key,""); }else{ if(value instanceof Map){ //说明这个属性是map Map mapValues = (Map)value; for(Object mapKey : mapValues.keySet()){ Object mapQueryValue = mapValues.get(mapKey); String mapQueryValueString = ""; if(mapQueryValue == null){ mapQueryValueString = ""; }else{ mapQueryValueString = VciBaseUtil.getStringValueFromObject(mapQueryValue); } requestTemplate.query(key + "[\"" + mapKey.toString() + "\"]",mapQueryValueString); } }else{ requestTemplate.query(key,VciBaseUtil.getStringValueFromObject(value)); } } } requestTemplate.body(Request.Body.empty()); }catch (Throwable e){ //可能不是json的类型 } } //需要传递用户的token //系统的token,用于权限验证 //日志的链接ID SessionInfo sessionInfo = VciBaseUtil.getCurrentUserSessionInfoNotException(); if(sessionInfo != null){ requestTemplate.header(TokenKeyConstant.USER_TOKEN_KEY,sessionInfo.getToken()); }else{ requestTemplate.header(TokenKeyConstant.USER_TOKEN_KEY,""); } requestTemplate.header(TokenKeyConstant.SYSTEM_PRIVATE_KEY,getPrivateTokenKey()); String logTraceId = MDC.get(TokenKeyConstant.TRACE_ID); if(logTraceId == null){ logTraceId = ""; } requestTemplate.header(TokenKeyConstant.LOG_TRACE_ID_KEY,logTraceId); //设置语言 requestTemplate.header(TokenKeyConstant.LANGUAGE_KEY,LocaleContextHolder.getLocale().toLanguageTag()); } public String getPrivateTokenKey() { return privateTokenKey; } public void setPrivateTokenKey(String privateTokenKey) { this.privateTokenKey = privateTokenKey; } }