+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *
+ * http://www.gnu.org/licenses/lgpl.html + *
+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springblade.core.cloud.annotation; + +import java.lang.annotation.*; + +/** + * header 版本 处理 + * + * @author L.cm + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface ApiVersion { + + /** + * header 路径中的版本 + * + * @return 版本号 + */ + String value() default ""; + +} diff --git a/blade-core-cloud/src/main/java/org/springblade/core/cloud/annotation/UrlVersion.java b/blade-core-cloud/src/main/java/org/springblade/core/cloud/annotation/UrlVersion.java new file mode 100644 index 0000000..c0622a8 --- /dev/null +++ b/blade-core-cloud/src/main/java/org/springblade/core/cloud/annotation/UrlVersion.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com). + *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *
+ * http://www.gnu.org/licenses/lgpl.html + *
+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springblade.core.cloud.annotation; + +import java.lang.annotation.*; + +/** + * 注解用于生成 requestMappingInfo 时候直接拼接路径规则,自动放置于方法路径开始部分 + * + * @author L.cm + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface UrlVersion { + + /** + * url 路径中的版本 + * + * @return 版本号 + */ + String value() default ""; +} diff --git a/blade-core-cloud/src/main/java/org/springblade/core/cloud/annotation/VersionMapping.java b/blade-core-cloud/src/main/java/org/springblade/core/cloud/annotation/VersionMapping.java new file mode 100644 index 0000000..93a0127 --- /dev/null +++ b/blade-core-cloud/src/main/java/org/springblade/core/cloud/annotation/VersionMapping.java @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com). + *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *
+ * http://www.gnu.org/licenses/lgpl.html + *
+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springblade.core.cloud.annotation; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestMapping; + +import java.lang.annotation.*; + +/** + * 版本号处理 + * + *
+ * 1. url 版本号:添加到 url 前 + * 2. Accept 版本:application/vnd.blade.VERSION+json + *
+ * + * @author L.cm + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@RequestMapping +@UrlVersion +@ApiVersion +@Validated +public @interface VersionMapping { + /** + * Alias for {@link RequestMapping#name}. + * @return {String[]} + */ + @AliasFor(annotation = RequestMapping.class) + String name() default ""; + + /** + * Alias for {@link RequestMapping#value}. + * @return {String[]} + */ + @AliasFor(annotation = RequestMapping.class) + String[] value() default {}; + + /** + * Alias for {@link RequestMapping#path}. + * @return {String[]} + */ + @AliasFor(annotation = RequestMapping.class) + String[] path() default {}; + + /** + * Alias for {@link RequestMapping#params}. + * @return {String[]} + */ + @AliasFor(annotation = RequestMapping.class) + String[] params() default {}; + + /** + * Alias for {@link RequestMapping#headers}. + * @return {String[]} + */ + @AliasFor(annotation = RequestMapping.class) + String[] headers() default {}; + + /** + * Alias for {@link RequestMapping#consumes}. + * @return {String[]} + */ + @AliasFor(annotation = RequestMapping.class) + String[] consumes() default {}; + + /** + * Alias for {@link RequestMapping#produces}. + * default json utf-8 + * @return {String[]} + */ + @AliasFor(annotation = RequestMapping.class) + String[] produces() default {}; + + /** + * Alias for {@link UrlVersion#value}. + * @return {String} + */ + @AliasFor(annotation = UrlVersion.class, attribute = "value") + String urlVersion() default ""; + + /** + * Alias for {@link ApiVersion#value}. + * @return {String} + */ + @AliasFor(annotation = ApiVersion.class, attribute = "value") + String apiVersion() default ""; + +} diff --git a/blade-core-cloud/src/main/java/org/springblade/core/cloud/config/BladeFeignConfiguration.java b/blade-core-cloud/src/main/java/org/springblade/core/cloud/config/BladeFeignConfiguration.java deleted file mode 100644 index 71ad4a9..0000000 --- a/blade-core-cloud/src/main/java/org/springblade/core/cloud/config/BladeFeignConfiguration.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com). - *- * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *
- * http://www.gnu.org/licenses/lgpl.html - *
- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springblade.core.cloud.config; - -import feign.RequestInterceptor; -import lombok.extern.slf4j.Slf4j; -import org.springblade.core.cloud.feign.BladeFeignRequestHeaderInterceptor; -import org.springblade.core.cloud.feign.FeignHystrixConcurrencyStrategy; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.cache.annotation.EnableCaching; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -/** - * WEB配置 - * - * @author Chill - */ -@Slf4j -@Configuration -@EnableCaching -@Order(Ordered.HIGHEST_PRECEDENCE) -public class BladeFeignConfiguration implements WebMvcConfigurer { - - @Bean - @ConditionalOnMissingBean - public RequestInterceptor requestInterceptor() { - return new BladeFeignRequestHeaderInterceptor(); - } - - @Bean - public FeignHystrixConcurrencyStrategy feignHystrixConcurrencyStrategy() { - return new FeignHystrixConcurrencyStrategy(); - } - -} diff --git a/blade-core-cloud/src/main/java/org/springblade/core/cloud/feign/BladeFallbackFactory.java b/blade-core-cloud/src/main/java/org/springblade/core/cloud/feign/BladeFallbackFactory.java new file mode 100644 index 0000000..b6a524d --- /dev/null +++ b/blade-core-cloud/src/main/java/org/springblade/core/cloud/feign/BladeFallbackFactory.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com). + *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *
+ * http://www.gnu.org/licenses/lgpl.html + *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.cloud.feign;
+
+import feign.Target;
+import feign.hystrix.FallbackFactory;
+import lombok.AllArgsConstructor;
+import org.springframework.cglib.proxy.Enhancer;
+
+/**
+ * 默认 Fallback,避免写过多fallback类
+ *
+ * @param
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.cloud.feign;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import feign.FeignException;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.api.ResultCode;
+import org.springblade.core.tool.jackson.JsonUtil;
+import org.springblade.core.tool.utils.ObjectUtil;
+import org.springframework.cglib.proxy.MethodInterceptor;
+import org.springframework.cglib.proxy.MethodProxy;
+import org.springframework.lang.Nullable;
+
+import java.lang.reflect.Method;
+import java.util.Objects;
+
+/**
+ * blade fallBack 代理处理
+ *
+ * @author L.cm
+ */
+@Slf4j
+@AllArgsConstructor
+public class BladeFeignFallback
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
@@ -17,36 +17,29 @@ package org.springblade.core.cloud.feign;
import feign.RequestInterceptor;
import feign.RequestTemplate;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.web.context.request.RequestContextHolder;
-import org.springframework.web.context.request.ServletRequestAttributes;
-
-import javax.servlet.http.HttpServletRequest;
-import java.util.Enumeration;
+import org.springblade.core.cloud.hystrix.BladeHttpHeadersContextHolder;
+import org.springframework.http.HttpHeaders;
/**
* feign 传递Request header
*
- * @author Chill
+ *
+ * https://blog.csdn.net/u014519194/article/details/77160958
+ * http://tietang.wang/2016/02/25/hystrix/Hystrix%E5%8F%82%E6%95%B0%E8%AF%A6%E8%A7%A3/
+ * https://github.com/Netflix/Hystrix/issues/92#issuecomment-260548068
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.cloud.feign;
+
+import feign.MethodMetadata;
+import org.springblade.core.cloud.annotation.ApiVersion;
+import org.springblade.core.cloud.annotation.UrlVersion;
+import org.springblade.core.cloud.version.BladeMediaType;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
+import org.springframework.cloud.openfeign.support.SpringMvcContract;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.http.HttpHeaders;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.List;
+
+/**
+ * 支持 blade-boot 的 版本 处理
+ *
+ * @see org.springblade.core.cloud.annotation.UrlVersion
+ * @see org.springblade.core.cloud.annotation.ApiVersion
+ * @author L.cm
+ */
+public class BladeSpringMvcContract extends SpringMvcContract {
+
+ public BladeSpringMvcContract(List
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springblade.core.cloud.feign;
+
+
+import org.springblade.core.launch.constant.AppConstant;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+
+import java.lang.annotation.*;
+
+/**
+ * 开启Feign注解
+ *
+ * @author Chill
+ */
+@Documented
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@EnableFeignClients(AppConstant.BASE_PACKAGES)
+public @interface EnableBladeFeign {
+ /**
+ * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
+ * declarations e.g.: {@code @ComponentScan("org.my.pkg")} instead of
+ * {@code @ComponentScan(basePackages="org.my.pkg")}.
+ *
+ * @return the array of 'basePackages'.
+ */
+ String[] value() default {};
+
+ /**
+ * Base packages to scan for annotated components.
+ *
+ * {@link #value()} is an alias for (and mutually exclusive with) this attribute.
+ *
+ * Use {@link #basePackageClasses()} for a type-safe alternative to String-based
+ * package names.
+ *
+ * @return the array of 'basePackages'.
+ */
+ String[] basePackages() default {};
+
+ /**
+ * Type-safe alternative to {@link #basePackages()} for specifying the packages to
+ * scan for annotated components. The package of each class specified will be scanned.
+ *
+ * Consider creating a special no-op marker class or interface in each package that
+ * serves no purpose other than being referenced by this attribute.
+ *
+ * @return the array of 'basePackageClasses'.
+ */
+ Class>[] basePackageClasses() default {};
+
+ /**
+ * A custom
- * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.gnu.org/licenses/lgpl.html
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.springblade.core.cloud.feign;
-
-import com.netflix.hystrix.HystrixThreadPoolKey;
-import com.netflix.hystrix.HystrixThreadPoolProperties;
-import com.netflix.hystrix.strategy.HystrixPlugins;
-import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
-import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariable;
-import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableLifecycle;
-import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier;
-import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook;
-import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher;
-import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy;
-import com.netflix.hystrix.strategy.properties.HystrixProperty;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.web.context.request.RequestAttributes;
-import org.springframework.web.context.request.RequestContextHolder;
-
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-/**
- * 自定义Feign的隔离策略
- *
- * @author Chill
- */
-public class FeignHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {
-
- private static final Logger log = LoggerFactory.getLogger(FeignHystrixConcurrencyStrategy.class);
- private HystrixConcurrencyStrategy delegate;
-
- public FeignHystrixConcurrencyStrategy() {
- try {
- this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy();
- if (this.delegate instanceof FeignHystrixConcurrencyStrategy) {
- // Welcome to singleton hell...
- return;
- }
- HystrixCommandExecutionHook commandExecutionHook =
- HystrixPlugins.getInstance().getCommandExecutionHook();
- HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier();
- HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher();
- HystrixPropertiesStrategy propertiesStrategy =
- HystrixPlugins.getInstance().getPropertiesStrategy();
- this.logCurrentStateOfHystrixPlugins(eventNotifier, metricsPublisher, propertiesStrategy);
- HystrixPlugins.reset();
- HystrixPlugins.getInstance().registerConcurrencyStrategy(this);
- HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);
- HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
- HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
- HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);
- } catch (Exception e) {
- log.error("Failed to register Sleuth Hystrix Concurrency Strategy", e);
- }
- }
-
- private void logCurrentStateOfHystrixPlugins(HystrixEventNotifier eventNotifier,
- HystrixMetricsPublisher metricsPublisher, HystrixPropertiesStrategy propertiesStrategy) {
- if (log.isDebugEnabled()) {
- log.debug("Current Hystrix plugins configuration is [" + "concurrencyStrategy ["
- + this.delegate + "]," + "eventNotifier [" + eventNotifier + "]," + "metricPublisher ["
- + metricsPublisher + "]," + "propertiesStrategy [" + propertiesStrategy + "]," + "]");
- log.debug("Registering Sleuth Hystrix Concurrency Strategy.");
- }
- }
-
- @Override
- public
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.cloud.http;
+
+import okhttp3.*;
+import okhttp3.internal.http.HttpHeaders;
+import okhttp3.internal.platform.Platform;
+import okio.Buffer;
+import okio.BufferedSource;
+import okio.GzipSource;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+import static okhttp3.internal.platform.Platform.INFO;
+
+/**
+ * An OkHttp interceptor which logs request and response information. Can be applied as an
+ * {@linkplain OkHttpClient#interceptors() application interceptor} or as a {@linkplain
+ * OkHttpClient#networkInterceptors() network interceptor}. The format of the logs created by
+ * this class should not be considered stable and may change slightly between releases. If you need
+ * a stable logging format, use your own interceptor.
+ *
+ * @author L.cm
+ */
+public final class HttpLoggingInterceptor implements Interceptor {
+ private static final Charset UTF8 = StandardCharsets.UTF_8;
+
+ public enum Level {
+ /**
+ * No logs.
+ */
+ NONE,
+ /**
+ * Logs request and response lines.
+ *
+ * Example:
+ * Example:
+ * Example:
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.cloud.http;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * OkHttp Slf4j logger
+ *
+ * @author L.cm
+ */
+@Slf4j
+public class OkHttpSlf4jLogger implements HttpLoggingInterceptor.Logger {
+ @Override
+ public void log(String message) {
+ log.info(message);
+ }
+}
diff --git a/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/RestTemplateConfiguration.java b/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/RestTemplateConfiguration.java
new file mode 100644
index 0000000..cc4f8e0
--- /dev/null
+++ b/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/RestTemplateConfiguration.java
@@ -0,0 +1,177 @@
+/**
+ * Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.cloud.http;
+
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.AllArgsConstructor;
+import org.springblade.core.cloud.hystrix.BladeHystrixAccountGetter;
+import org.springblade.core.cloud.props.BladeHystrixHeadersProperties;
+import org.springblade.core.tool.utils.Charsets;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.cloud.client.loadbalancer.LoadBalanced;
+import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory;
+import org.springframework.cloud.commons.httpclient.OkHttpClientFactory;
+import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.lang.Nullable;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Http RestTemplateHeaderInterceptor 配置
+ *
+ * @author L.cm
+ */
+@Configuration
+@ConditionalOnClass(okhttp3.OkHttpClient.class)
+@AllArgsConstructor
+public class RestTemplateConfiguration {
+ private final ObjectMapper objectMapper;
+
+ /**
+ * dev, test 环境打印出BODY
+ * @return HttpLoggingInterceptor
+ */
+ @Bean("httpLoggingInterceptor")
+ @Profile({"dev", "test"})
+ public HttpLoggingInterceptor testLoggingInterceptor() {
+ HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(new OkHttpSlf4jLogger());
+ interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
+ return interceptor;
+ }
+
+ /**
+ * ontest 环境 打印 请求头
+ * @return HttpLoggingInterceptor
+ */
+ @Bean("httpLoggingInterceptor")
+ @Profile("ontest")
+ public HttpLoggingInterceptor onTestLoggingInterceptor() {
+ HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(new OkHttpSlf4jLogger());
+ interceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
+ return interceptor;
+ }
+
+ /**
+ * prod 环境只打印请求url
+ * @return HttpLoggingInterceptor
+ */
+ @Bean("httpLoggingInterceptor")
+ @Profile("prod")
+ public HttpLoggingInterceptor prodLoggingInterceptor() {
+ HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(new OkHttpSlf4jLogger());
+ interceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
+ return interceptor;
+ }
+
+ /**
+ * okhttp3 链接池配置
+ * @param connectionPoolFactory 链接池配置
+ * @param httpClientProperties httpClient配置
+ * @return okhttp3.ConnectionPool
+ */
+ @Bean
+ @ConditionalOnMissingBean(okhttp3.ConnectionPool.class)
+ public okhttp3.ConnectionPool httpClientConnectionPool(
+ FeignHttpClientProperties httpClientProperties,
+ OkHttpClientConnectionPoolFactory connectionPoolFactory) {
+ Integer maxTotalConnections = httpClientProperties.getMaxConnections();
+ Long timeToLive = httpClientProperties.getTimeToLive();
+ TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
+ return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
+ }
+
+ /**
+ * 配置OkHttpClient
+ * @param httpClientFactory httpClient 工厂
+ * @param connectionPool 链接池配置
+ * @param httpClientProperties httpClient配置
+ * @param interceptor 拦截器
+ * @return OkHttpClient
+ */
+ @Bean
+ @ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
+ public okhttp3.OkHttpClient httpClient(
+ OkHttpClientFactory httpClientFactory,
+ okhttp3.ConnectionPool connectionPool,
+ FeignHttpClientProperties httpClientProperties,
+ HttpLoggingInterceptor interceptor) {
+ Boolean followRedirects = httpClientProperties.isFollowRedirects();
+ Integer connectTimeout = httpClientProperties.getConnectionTimeout();
+ return httpClientFactory.createBuilder(httpClientProperties.isDisableSslValidation())
+ .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
+ .writeTimeout(30, TimeUnit.SECONDS)
+ .readTimeout(30, TimeUnit.SECONDS)
+ .followRedirects(followRedirects)
+ .connectionPool(connectionPool)
+ .addInterceptor(interceptor)
+ .build();
+ }
+
+ @Bean
+ public RestTemplateHeaderInterceptor requestHeaderInterceptor(
+ @Autowired(required = false) @Nullable BladeHystrixAccountGetter accountGetter,
+ BladeHystrixHeadersProperties properties) {
+ return new RestTemplateHeaderInterceptor(accountGetter,properties);
+ }
+
+ /**
+ * 普通的 RestTemplate,不透传请求头,一般只做外部 http 调用
+ * @param httpClient OkHttpClient
+ * @return RestTemplate
+ */
+ @Bean
+ @ConditionalOnMissingBean(RestTemplate.class)
+ public RestTemplate restTemplate(okhttp3.OkHttpClient httpClient) {
+ RestTemplate restTemplate = new RestTemplate(new OkHttp3ClientHttpRequestFactory(httpClient));
+ configMessageConverters(restTemplate.getMessageConverters());
+ return restTemplate;
+ }
+
+ /**
+ * 支持负载均衡的 LbRestTemplate
+ * @param httpClient OkHttpClient
+ * @param interceptor RestTemplateHeaderInterceptor
+ * @return LbRestTemplate
+ */
+ @Bean
+ @LoadBalanced
+ @ConditionalOnMissingBean(LbRestTemplate.class)
+ public LbRestTemplate lbRestTemplate(okhttp3.OkHttpClient httpClient, RestTemplateHeaderInterceptor interceptor) {
+ LbRestTemplate lbRestTemplate = new LbRestTemplate(new OkHttp3ClientHttpRequestFactory(httpClient));
+ lbRestTemplate.setInterceptors(Collections.singletonList(interceptor));
+ configMessageConverters(lbRestTemplate.getMessageConverters());
+ return lbRestTemplate;
+ }
+
+ private void configMessageConverters(List
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.cloud.http;
+
+import lombok.AllArgsConstructor;
+import org.springblade.core.cloud.hystrix.BladeHttpHeadersContextHolder;
+import org.springblade.core.cloud.hystrix.BladeHystrixAccountGetter;
+import org.springblade.core.cloud.props.BladeHystrixHeadersProperties;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpRequest;
+import org.springframework.http.client.ClientHttpRequestExecution;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.lang.Nullable;
+
+import java.io.IOException;
+
+/**
+ * RestTemplateHeaderInterceptor 传递Request header
+ *
+ * @author L.cm
+ */
+@AllArgsConstructor
+public class RestTemplateHeaderInterceptor implements ClientHttpRequestInterceptor {
+ @Nullable
+ private final BladeHystrixAccountGetter accountGetter;
+ private final BladeHystrixHeadersProperties properties;
+
+ @Override
+ public ClientHttpResponse intercept(
+ HttpRequest request, byte[] bytes,
+ ClientHttpRequestExecution execution) throws IOException {
+ HttpHeaders headers = BladeHttpHeadersContextHolder.get();
+ // 考虑2中情况 1. RestTemplate 不是用 hystrix 2. 使用 hystrix
+ if (headers == null) {
+ headers = BladeHttpHeadersContextHolder.toHeaders(accountGetter, properties);
+ }
+ if (headers != null && !headers.isEmpty()) {
+ HttpHeaders httpHeaders = request.getHeaders();
+ headers.forEach((key, values) -> {
+ values.forEach(value -> httpHeaders.add(key, value));
+ });
+ }
+ return execution.execute(request, bytes);
+ }
+}
diff --git a/blade-core-cloud/src/main/java/org/springblade/core/cloud/hystrix/BladeAccountGetter.java b/blade-core-cloud/src/main/java/org/springblade/core/cloud/hystrix/BladeAccountGetter.java
new file mode 100644
index 0000000..43c967c
--- /dev/null
+++ b/blade-core-cloud/src/main/java/org/springblade/core/cloud/hystrix/BladeAccountGetter.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.cloud.hystrix;
+
+import org.springblade.core.secure.BladeUser;
+import org.springblade.core.secure.utils.SecureUtil;
+import org.springblade.core.tool.utils.Charsets;
+import org.springblade.core.tool.utils.UrlUtil;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 用户信息获取器
+ *
+ * @author Chill
+ */
+public class BladeAccountGetter implements BladeHystrixAccountGetter {
+
+ @Override
+ public String get(HttpServletRequest request) {
+ BladeUser account = SecureUtil.getUser();
+ if (account == null) {
+ return null;
+ }
+ // 增加用户头, 123[admin]
+ String xAccount = String.format("%s[%s]", account.getUserId(), account.getUserName());
+ return UrlUtil.encodeURL(xAccount, Charsets.UTF_8);
+ }
+
+}
diff --git a/blade-core-cloud/src/main/java/org/springblade/core/cloud/hystrix/BladeHttpHeadersCallable.java b/blade-core-cloud/src/main/java/org/springblade/core/cloud/hystrix/BladeHttpHeadersCallable.java
new file mode 100644
index 0000000..2bd1d02
--- /dev/null
+++ b/blade-core-cloud/src/main/java/org/springblade/core/cloud/hystrix/BladeHttpHeadersCallable.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.cloud.hystrix;
+
+import org.springblade.core.cloud.props.BladeHystrixHeadersProperties;
+import org.springframework.http.HttpHeaders;
+import org.springframework.lang.Nullable;
+
+import java.util.concurrent.Callable;
+
+/**
+ * HttpHeaders hystrix Callable
+ *
+ * @param
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.cloud.hystrix;
+
+import org.springblade.core.cloud.props.BladeHystrixHeadersProperties;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.core.tool.utils.WebUtil;
+import org.springframework.core.NamedThreadLocal;
+import org.springframework.http.HttpHeaders;
+import org.springframework.lang.Nullable;
+import org.springframework.util.PatternMatchUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.List;
+
+/**
+ * HttpHeadersContext
+ *
+ * @author L.cm
+ */
+public class BladeHttpHeadersContextHolder {
+ private static final ThreadLocal
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.cloud.hystrix;
+
+
+import org.springframework.lang.Nullable;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Blade 用户信息获取器,用于请求头传递
+ *
+ * @author L.cm
+ */
+public interface BladeHystrixAccountGetter {
+
+ /**
+ * 账号信息获取器
+ *
+ * @param request HttpServletRequest
+ * @return account 信息
+ */
+ @Nullable
+ String get(HttpServletRequest request);
+}
diff --git a/blade-core-cloud/src/main/java/org/springblade/core/cloud/hystrix/BladeHystrixAutoConfiguration.java b/blade-core-cloud/src/main/java/org/springblade/core/cloud/hystrix/BladeHystrixAutoConfiguration.java
new file mode 100644
index 0000000..9462c8a
--- /dev/null
+++ b/blade-core-cloud/src/main/java/org/springblade/core/cloud/hystrix/BladeHystrixAutoConfiguration.java
@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.cloud.hystrix;
+
+import com.netflix.hystrix.Hystrix;
+import com.netflix.hystrix.strategy.HystrixPlugins;
+import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
+import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier;
+import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook;
+import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher;
+import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy;
+import org.springblade.core.cloud.props.BladeHystrixHeadersProperties;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.lang.Nullable;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * Hystrix 配置
+ *
+ * @author L.cm
+ */
+@Configuration
+@ConditionalOnClass(Hystrix.class)
+@EnableConfigurationProperties(BladeHystrixHeadersProperties.class)
+public class BladeHystrixAutoConfiguration {
+ @Nullable
+ @Autowired(required = false)
+ private HystrixConcurrencyStrategy existingConcurrencyStrategy;
+ @Nullable
+ @Autowired(required = false)
+ private BladeHystrixAccountGetter accountGetter;
+ @Autowired
+ private BladeHystrixHeadersProperties properties;
+
+ @PostConstruct
+ public void init() {
+ // Keeps references of existing Hystrix plugins.
+ HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier();
+ HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher();
+ HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance().getPropertiesStrategy();
+ HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins.getInstance().getCommandExecutionHook();
+
+ HystrixPlugins.reset();
+
+ // Registers existing plugins excepts the Concurrent Strategy plugin.
+ HystrixConcurrencyStrategy strategy = new BladeHystrixConcurrencyStrategy(existingConcurrencyStrategy, accountGetter, properties);
+ HystrixPlugins.getInstance().registerConcurrencyStrategy(strategy);
+ HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
+ HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
+ HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);
+ HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);
+ }
+
+}
diff --git a/blade-core-cloud/src/main/java/org/springblade/core/cloud/hystrix/BladeHystrixConcurrencyStrategy.java b/blade-core-cloud/src/main/java/org/springblade/core/cloud/hystrix/BladeHystrixConcurrencyStrategy.java
new file mode 100644
index 0000000..76c1fa5
--- /dev/null
+++ b/blade-core-cloud/src/main/java/org/springblade/core/cloud/hystrix/BladeHystrixConcurrencyStrategy.java
@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.cloud.hystrix;
+
+import com.netflix.hystrix.HystrixThreadPoolKey;
+import com.netflix.hystrix.HystrixThreadPoolProperties;
+import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
+import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariable;
+import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableLifecycle;
+import com.netflix.hystrix.strategy.properties.HystrixProperty;
+import lombok.AllArgsConstructor;
+import org.springblade.core.cloud.props.BladeHystrixHeadersProperties;
+import org.springframework.lang.Nullable;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Hystrix传递ThreaLocal中的一些变量
+ *
+ *
+ * https://github.com/Netflix/Hystrix/issues/92#issuecomment-260548068
+ * https://github.com/spring-cloud/spring-cloud-sleuth/issues/39
+ * https://github.com/spring-cloud/spring-cloud-netflix/tree/master/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/security
+ * https://github.com/spring-projects/spring-security/blob/master/core/src/main/java/org/springframework/security/concurrent/DelegatingSecurityContextCallable.java
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.cloud.props;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.lang.Nullable;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Hystrix Headers 配置
+ *
+ * @author L.cm
+ */
+@Getter
+@Setter
+@RefreshScope
+@ConfigurationProperties("blade.hystrix.headers")
+public class BladeHystrixHeadersProperties {
+
+ /**
+ * 用于 聚合层 向调用层传递用户信息 的请求头,默认:x-blade-account
+ */
+ private String account = "X-Blade-Account";
+
+ /**
+ * RestTemplate 和 Fegin 透传到下层的 Headers 名称表达式
+ */
+ @Nullable
+ private String pattern = "Blade*";
+
+ /**
+ * RestTemplate 和 Fegin 透传到下层的 Headers 名称列表
+ */
+ private List
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springblade.core.cloud.version;
+
+import lombok.Getter;
+import org.springframework.http.MediaType;
+
+/**
+ * blade Media Types,application/vnd.github.VERSION+json
+ *
+ *
+ * https://developer.github.com/v3/media/
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.cloud.version;
+
+import org.springblade.core.cloud.annotation.ApiVersion;
+import org.springblade.core.cloud.annotation.UrlVersion;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.lang.Nullable;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+
+/**
+ * url版本号处理 和 header 版本处理
+ *
+ *
+ * url: /v1/user/{id}
+ * header: Accept application/vnd.blade.VERSION+json
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.cloud.version;
+
+import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
+import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+/**
+ * url版本号处理
+ *
+ * @author L.cm
+ */
+public class BladeWebMvcRegistrations implements WebMvcRegistrations {
+ @Override
+ public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
+ return new BladeRequestMappingHandlerMapping();
+ }
+
+ @Override
+ public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
+ return null;
+ }
+
+ @Override
+ public ExceptionHandlerExceptionResolver getExceptionHandlerExceptionResolver() {
+ return null;
+ }
+}
diff --git a/blade-core-cloud/src/main/java/org/springblade/core/cloud/version/VersionMappingAutoConfiguration.java b/blade-core-cloud/src/main/java/org/springblade/core/cloud/version/VersionMappingAutoConfiguration.java
new file mode 100644
index 0000000..2276e23
--- /dev/null
+++ b/blade-core-cloud/src/main/java/org/springblade/core/cloud/version/VersionMappingAutoConfiguration.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.cloud.version;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * url版本号处理
+ *
+ * 参考:https://gitee.com/lianqu1990/spring-boot-starter-version-mapping
+ *
+ * @author L.cm
+ */
+@Configuration
+@ConditionalOnWebApplication
+public class VersionMappingAutoConfiguration {
+ @Bean
+ public WebMvcRegistrations bladeWebMvcRegistrations() {
+ return new BladeWebMvcRegistrations();
+ }
+}
diff --git a/blade-core-cloud/src/main/java/org/springframework/cloud/openfeign/BladeFeignClientsRegistrar.java b/blade-core-cloud/src/main/java/org/springframework/cloud/openfeign/BladeFeignClientsRegistrar.java
new file mode 100644
index 0000000..b6c635c
--- /dev/null
+++ b/blade-core-cloud/src/main/java/org/springframework/cloud/openfeign/BladeFeignClientsRegistrar.java
@@ -0,0 +1,230 @@
+/**
+ * Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.cloud.openfeign;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.springblade.core.cloud.feign.BladeFeignAutoConfiguration;
+import org.springframework.beans.factory.BeanClassLoaderAware;
+import org.springframework.beans.factory.config.BeanDefinitionHolder;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.context.EnvironmentAware;
+import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.env.Environment;
+import org.springframework.core.io.support.SpringFactoriesLoader;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.lang.Nullable;
+import org.springframework.util.StringUtils;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * feign 自动配置
+ *
+ * @author L.cm
+ */
+@NoArgsConstructor
+public class BladeFeignClientsRegistrar implements ImportBeanDefinitionRegistrar, BeanClassLoaderAware, EnvironmentAware {
+ @Getter
+ private ClassLoader beanClassLoader;
+ @Getter
+ private Environment environment;
+
+ @Override
+ public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
+ registerFeignClients(metadata, registry);
+ }
+
+ @Override
+ public void setBeanClassLoader(ClassLoader classLoader) {
+ this.beanClassLoader = classLoader;
+ }
+
+ private void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
+ List
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
@@ -20,17 +20,17 @@ import org.springblade.core.launch.props.BladeProperties;
import org.springblade.core.launch.server.ServerInfo;
import org.springblade.core.log.model.LogAbstract;
import org.springblade.core.secure.utils.SecureUtil;
+import org.springblade.core.tool.utils.DateUtil;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.UrlUtil;
import org.springblade.core.tool.utils.WebUtil;
import javax.servlet.http.HttpServletRequest;
-import java.time.LocalDateTime;
/**
- * INet 相关工具
+ * Log 相关工具
*
- * @author L.cm
+ * @author Chill
*/
public class LogAbstractUtil {
@@ -61,7 +61,7 @@ public class LogAbstractUtil {
logAbstract.setServerHost(serverInfo.getHostName());
logAbstract.setServerIp(serverInfo.getIpWithPort());
logAbstract.setEnv(bladeProperties.getEnv());
- logAbstract.setCreateTime(LocalDateTime.now());
+ logAbstract.setCreateTime(DateUtil.now());
//这里判断一下params为null的情况,否则blade-log服务在解析该字段的时候,可能会报出NPE
if (logAbstract.getParams() == null) {
diff --git a/blade-core-mybatis/pom.xml b/blade-core-mybatis/pom.xml
index 0550881..439025d 100644
--- a/blade-core-mybatis/pom.xml
+++ b/blade-core-mybatis/pom.xml
@@ -5,7 +5,7 @@
+ * Note: This method actually returns an {@link BladeConversionService}
+ * instance. However, the {@code ConversionService} signature has been preserved for
+ * binary compatibility.
+ * @return the shared {@code BladeConversionService} instance (never{@code null})
+ */
+ public static GenericConversionService getInstance() {
+ BladeConversionService sharedInstance = BladeConversionService.SHARED_INSTANCE;
+ if (sharedInstance == null) {
+ synchronized (BladeConversionService.class) {
+ sharedInstance = BladeConversionService.SHARED_INSTANCE;
+ if (sharedInstance == null) {
+ sharedInstance = new BladeConversionService();
+ BladeConversionService.SHARED_INSTANCE = sharedInstance;
+ }
+ }
+ }
+ return sharedInstance;
+ }
+
+}
diff --git a/blade-core-tool/src/main/java/org/springblade/core/tool/convert/BladeConverter.java b/blade-core-tool/src/main/java/org/springblade/core/tool/convert/BladeConverter.java
new file mode 100644
index 0000000..b0af763
--- /dev/null
+++ b/blade-core-tool/src/main/java/org/springblade/core/tool/convert/BladeConverter.java
@@ -0,0 +1,65 @@
+package org.springblade.core.tool.convert;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.tool.support.Try;
+import org.springblade.core.tool.utils.ClassUtil;
+import org.springblade.core.tool.utils.ConvertUtil;
+import org.springblade.core.tool.utils.ReflectUtil;
+import org.springframework.cglib.core.Converter;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.lang.Nullable;
+
+import java.lang.reflect.Field;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * 组合 spring cglib Converter 和 spring ConversionService
+ *
+ * @author L.cm
+ */
+@Slf4j
+@AllArgsConstructor
+public class BladeConverter implements Converter {
+ private static final ConcurrentMap
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package org.springblade.core.tool.utils;
diff --git a/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ReflectUtil.java b/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ReflectUtil.java
new file mode 100644
index 0000000..6b54d02
--- /dev/null
+++ b/blade-core-tool/src/main/java/org/springblade/core/tool/utils/ReflectUtil.java
@@ -0,0 +1,166 @@
+package org.springblade.core.tool.utils;
+
+import lombok.experimental.UtilityClass;
+import org.springframework.beans.BeansException;
+import org.springframework.cglib.core.CodeGenerationException;
+import org.springframework.core.convert.Property;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.lang.Nullable;
+import org.springframework.util.ReflectionUtils;
+
+import java.beans.PropertyDescriptor;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 反射工具类
+ *
+ * @author L.cm
+ */
+@UtilityClass
+public class ReflectUtil extends ReflectionUtils {
+
+ /**
+ * 获取 Bean 的所有 get方法
+ *
+ * @param type 类
+ * @return PropertyDescriptor数组
+ */
+ public static PropertyDescriptor[] getBeanGetters(Class type) {
+ return getPropertiesHelper(type, true, false);
+ }
+
+ /**
+ * 获取 Bean 的所有 set方法
+ *
+ * @param type 类
+ * @return PropertyDescriptor数组
+ */
+ public static PropertyDescriptor[] getBeanSetters(Class type) {
+ return getPropertiesHelper(type, false, true);
+ }
+
+ /**
+ * 获取 Bean 的所有 PropertyDescriptor
+ *
+ * @param type 类
+ * @param read 读取方法
+ * @param write 写方法
+ * @return PropertyDescriptor数组
+ */
+ public static PropertyDescriptor[] getPropertiesHelper(Class type, boolean read, boolean write) {
+ try {
+ PropertyDescriptor[] all = BeanUtil.getPropertyDescriptors(type);
+ if (read && write) {
+ return all;
+ } else {
+ List
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.transaction.annotation;
+
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+import java.lang.annotation.*;
+
+/**
+ * Seata启动注解配置
+ *
+ * @author Chill
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+@EnableDiscoveryClient
+@EnableCircuitBreaker
+@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
+public @interface SeataCloudApplication {
+
+}
diff --git a/blade-core-transaction/src/main/java/org/springblade/core/transaction/config/DataSourceConfiguration.java b/blade-core-transaction/src/main/java/org/springblade/core/transaction/config/DataSourceConfiguration.java
new file mode 100644
index 0000000..194c03c
--- /dev/null
+++ b/blade-core-transaction/src/main/java/org/springblade/core/transaction/config/DataSourceConfiguration.java
@@ -0,0 +1,79 @@
+/**
+ * Copyright (c) 2018-2028, lengleng (wangiegie@gmail.com).
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.transaction.config;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
+import io.seata.rm.datasource.DataSourceProxy;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.mybatis.spring.SqlSessionTemplate;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+
+import javax.sql.DataSource;
+
+/**
+ * 分布式事务数据源配置
+ *
+ * @author Chill
+ */
+@Configuration
+public class DataSourceConfiguration {
+
+ @Bean(name = "sqlSessionFactory")
+ public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
+ MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
+ bean.setDataSource(dataSourceProxy);
+ ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
+ bean.setMapperLocations(resolver.getResources("classpath:org/springblade/**/mapper/*Mapper.xml"));
+
+ SqlSessionFactory factory = null;
+ try {
+ factory = bean.getObject();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ return factory;
+ }
+
+ @Bean
+ public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
+ return new SqlSessionTemplate(sqlSessionFactory);
+ }
+
+ /**
+ * 从配置文件获取属性构造datasource
+ */
+ @Bean
+ @ConfigurationProperties(prefix = "spring.datasource")
+ public DruidDataSource druidDataSource() {
+ return new DruidDataSource();
+ }
+
+ /**
+ * 构造datasource代理对象
+ */
+ @Primary
+ @Bean("dataSource")
+ public DataSourceProxy dataSourceProxy(DataSource druidDataSource) {
+ return new DataSourceProxy(druidDataSource);
+ }
+
+}
diff --git a/blade-core-transaction/src/main/resources/registry.conf b/blade-core-transaction/src/main/resources/registry.conf
new file mode 100644
index 0000000..da0dda8
--- /dev/null
+++ b/blade-core-transaction/src/main/resources/registry.conf
@@ -0,0 +1,20 @@
+registry {
+ # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
+ type = "nacos"
+
+ nacos {
+ serverAddr = "localhost"
+ namespace = ""
+ cluster = "default"
+ }
+}
+
+config {
+ # file、nacos 、apollo、zk、consul、etcd3
+ type = "nacos"
+
+ nacos {
+ serverAddr = "localhost"
+ namespace = ""
+ }
+}
diff --git a/pom.xml b/pom.xml
index 0bc975f..bbd5817 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
@Configuration
for all feign clients. Can contain override
+ * @Bean
definition for the pieces that make up the client, for instance
+ * {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
+ */
+ Class>[] defaultConfiguration() default {};
+
+ /**
+ * List of classes annotated with @FeignClient. If not empty, disables classpath scanning.
+ *
+ * @return
+ */
+ Class>[] clients() default {};
+}
diff --git a/blade-core-cloud/src/main/java/org/springblade/core/cloud/feign/FeignHystrixConcurrencyStrategy.java b/blade-core-cloud/src/main/java/org/springblade/core/cloud/feign/FeignHystrixConcurrencyStrategy.java
deleted file mode 100644
index 32d883e..0000000
--- a/blade-core-cloud/src/main/java/org/springblade/core/cloud/feign/FeignHystrixConcurrencyStrategy.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/**
- * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
- * {@code
+ * --> POST /greeting http/1.1 (3-byte body)
+ *
+ * <-- 200 OK (22ms, 6-byte body)
+ * }
+ */
+ BASIC,
+ /**
+ * Logs request and response lines and their respective headers.
+ *
+ * {@code
+ * --> POST /greeting http/1.1
+ * Host: example.com
+ * Content-Type: plain/text
+ * Content-Length: 3
+ * --> END POST
+ *
+ * <-- 200 OK (22ms)
+ * Content-Type: plain/text
+ * Content-Length: 6
+ * <-- END HTTP
+ * }
+ */
+ HEADERS,
+ /**
+ * Logs request and response lines and their respective headers and bodies (if present).
+ *
+ * {@code
+ * --> POST /greeting http/1.1
+ * Host: example.com
+ * Content-Type: plain/text
+ * Content-Length: 3
+ *
+ * Hi?
+ * --> END POST
+ *
+ * <-- 200 OK (22ms)
+ * Content-Type: plain/text
+ * Content-Length: 6
+ *
+ * Hello!
+ * <-- END HTTP
+ * }
+ */
+ BODY
+ }
+
+ public interface Logger {
+ /**
+ * log
+ * @param message message
+ */
+ void log(String message);
+
+ /**
+ * A {@link Logger} defaults output appropriate for the current platform.
+ */
+ Logger DEFAULT = message -> Platform.get().log(INFO, message, null);
+ }
+
+ public HttpLoggingInterceptor() {
+ this(Logger.DEFAULT);
+ }
+
+ public HttpLoggingInterceptor(Logger logger) {
+ this.logger = logger;
+ }
+
+ private final Logger logger;
+
+ private volatile Level level = Level.NONE;
+
+ /**
+ * Change the level at which this interceptor logs.
+ * @param level log Level
+ * @return HttpLoggingInterceptor
+ */
+ public HttpLoggingInterceptor setLevel(Level level) {
+ Objects.requireNonNull(level, "level == null. Use Level.NONE instead.");
+ this.level = level;
+ return this;
+ }
+
+ public Level getLevel() {
+ return level;
+ }
+
+ private String gzip = "gzip";
+ private String contentEncoding = "Content-Encoding";
+
+ @Override
+ public Response intercept(Chain chain) throws IOException {
+ Level level = this.level;
+
+ Request request = chain.request();
+ if (level == Level.NONE) {
+ return chain.proceed(request);
+ }
+
+ boolean logBody = level == Level.BODY;
+ boolean logHeaders = logBody || level == Level.HEADERS;
+
+ RequestBody requestBody = request.body();
+ boolean hasRequestBody = requestBody != null;
+
+ Connection connection = chain.connection();
+ String requestStartMessage = "--> "
+ + request.method()
+ + ' ' + request.url()
+ + (connection != null ? " " + connection.protocol() : "");
+ if (!logHeaders && hasRequestBody) {
+ requestStartMessage += " (" + requestBody.contentLength() + "-byte body)";
+ }
+ logger.log(requestStartMessage);
+
+ if (logHeaders) {
+ if (hasRequestBody) {
+ // Request body headers are only present when installed as a network interceptor. Force
+ // them to be included (when available) so there values are known.
+ if (requestBody.contentType() != null) {
+ logger.log("Content-Type: " + requestBody.contentType());
+ }
+ if (requestBody.contentLength() != -1) {
+ logger.log("Content-Length: " + requestBody.contentLength());
+ }
+ }
+
+ Headers headers = request.headers();
+ for (int i = 0, count = headers.size(); i < count; i++) {
+ String name = headers.name(i);
+ // Skip headers from the request body as they are explicitly logged above.
+ if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) {
+ logger.log(name + ": " + headers.value(i));
+ }
+ }
+
+ if (!logBody || !hasRequestBody) {
+ logger.log("--> END " + request.method());
+ } else if (bodyHasUnknownEncoding(request.headers())) {
+ logger.log("--> END " + request.method() + " (encoded body omitted)");
+ } else {
+ Buffer buffer = new Buffer();
+ requestBody.writeTo(buffer);
+
+ Charset charset = UTF8;
+ MediaType contentType = requestBody.contentType();
+ if (contentType != null) {
+ charset = contentType.charset(UTF8);
+ }
+
+ logger.log("");
+ if (isPlaintext(buffer)) {
+ logger.log(buffer.readString(charset));
+ logger.log("--> END " + request.method()
+ + " (" + requestBody.contentLength() + "-byte body)");
+ } else {
+ logger.log("--> END " + request.method() + " (binary "
+ + requestBody.contentLength() + "-byte body omitted)");
+ }
+ }
+ }
+
+ long startNs = System.nanoTime();
+ Response response;
+ try {
+ response = chain.proceed(request);
+ } catch (Exception e) {
+ logger.log("<-- HTTP FAILED: " + e);
+ throw e;
+ }
+ long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
+
+ ResponseBody responseBody = response.body();
+ long contentLength = responseBody.contentLength();
+ String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
+ logger.log("<-- "
+ + response.code()
+ + (response.message().isEmpty() ? "" : ' ' + response.message())
+ + ' ' + response.request().url()
+ + " (" + tookMs + "ms" + (!logHeaders ? ", " + bodySize + " body" : "") + ')');
+
+ if (logHeaders) {
+ Headers headers = response.headers();
+ for (int i = 0, count = headers.size(); i < count; i++) {
+ logger.log(headers.name(i) + ": " + headers.value(i));
+ }
+
+ if (!logBody || !HttpHeaders.hasBody(response)) {
+ logger.log("<-- END HTTP");
+ } else if (bodyHasUnknownEncoding(response.headers())) {
+ logger.log("<-- END HTTP (encoded body omitted)");
+ } else {
+ BufferedSource source = responseBody.source();
+ // Buffer the entire body.
+ source.request(Long.MAX_VALUE);
+ Buffer buffer = source.buffer();
+
+ Long gzippedLength = null;
+ if (gzip.equalsIgnoreCase(headers.get(contentEncoding))) {
+ gzippedLength = buffer.size();
+ GzipSource gzippedResponseBody = null;
+ try {
+ gzippedResponseBody = new GzipSource(buffer.clone());
+ buffer = new Buffer();
+ buffer.writeAll(gzippedResponseBody);
+ } finally {
+ if (gzippedResponseBody != null) {
+ gzippedResponseBody.close();
+ }
+ }
+ }
+
+ Charset charset = UTF8;
+ MediaType contentType = responseBody.contentType();
+ if (contentType != null) {
+ charset = contentType.charset(UTF8);
+ }
+
+ if (!isPlaintext(buffer)) {
+ logger.log("");
+ logger.log("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)");
+ return response;
+ }
+
+ if (contentLength != 0) {
+ logger.log("");
+ logger.log(buffer.clone().readString(charset));
+ }
+
+ if (gzippedLength != null) {
+ logger.log("<-- END HTTP (" + buffer.size() + "-byte, "
+ + gzippedLength + "-gzipped-byte body)");
+ } else {
+ logger.log("<-- END HTTP (" + buffer.size() + "-byte body)");
+ }
+ }
+ }
+
+ return response;
+ }
+
+ /**
+ * Returns true if the body in question probably contains human readable text. Uses a small sample
+ * of code points to detect unicode control characters commonly used in binary file signatures.
+ */
+ private static int plainCnt = 16;
+ private static boolean isPlaintext(Buffer buffer) {
+ try {
+ Buffer prefix = new Buffer();
+ long byteCount = buffer.size() < 64 ? buffer.size() : 64;
+ buffer.copyTo(prefix, 0, byteCount);
+ for (int i = 0; i < plainCnt; i++) {
+ if (prefix.exhausted()) {
+ break;
+ }
+ int codePoint = prefix.readUtf8CodePoint();
+ if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
+ return false;
+ }
+ }
+ return true;
+ } catch (EOFException e) {
+ // Truncated UTF-8 sequence.
+ return false;
+ }
+ }
+
+ private boolean bodyHasUnknownEncoding(Headers headers) {
+ String contentEncoding = headers.get("Content-Encoding");
+ return contentEncoding != null
+ && !"identity".equalsIgnoreCase(contentEncoding)
+ && !"gzip".equalsIgnoreCase(contentEncoding);
+ }
+}
diff --git a/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/LbRestTemplate.java b/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/LbRestTemplate.java
new file mode 100644
index 0000000..b4ac109
--- /dev/null
+++ b/blade-core-cloud/src/main/java/org/springblade/core/cloud/http/LbRestTemplate.java
@@ -0,0 +1,27 @@
+package org.springblade.core.cloud.http;
+
+import org.springframework.http.client.ClientHttpRequestFactory;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.List;
+
+/**
+ * Loadbalancer RestTemplate
+ *
+ * @author L.cm
+ */
+public class LbRestTemplate extends RestTemplate {
+
+ public LbRestTemplate() {
+ super();
+ }
+
+ public LbRestTemplate(ClientHttpRequestFactory requestFactory) {
+ super(requestFactory);
+ }
+
+ public LbRestTemplate(List