🎉 3.0.0.RELEASE 架构升级适配 SpringCloud 2020

This commit is contained in:
smallchill 2021-01-06 15:56:41 +08:00
parent 231fbd653f
commit 17d8fce5f7
79 changed files with 799 additions and 788 deletions

View File

@ -1,7 +1,7 @@
<p align="center"> <p align="center">
<img src="https://img.shields.io/badge/license-LGPL%20v3-blue.svg" alt="Build Status"> <img src="https://img.shields.io/badge/license-LGPL%20v3-blue.svg" alt="Build Status">
<img src="https://img.shields.io/badge/Spring%20Cloud-Hoxton.SR8-blue.svg" alt="Coverage Status"> <img src="https://img.shields.io/badge/Spring%20Cloud-2020-blue.svg" alt="Coverage Status">
<img src="https://img.shields.io/badge/Spring%20Boot-2.2.11.RELEASE-blue.svg" alt="Downloads"> <img src="https://img.shields.io/badge/Spring%20Boot-2.4.1-blue.svg" alt="Downloads">
</p> </p>
## SpringBlade微服务开发平台 ## SpringBlade微服务开发平台
@ -14,7 +14,7 @@
* 极简封装了多租户底层用更少的代码换来拓展性更强的SaaS多租户系统。 * 极简封装了多租户底层用更少的代码换来拓展性更强的SaaS多租户系统。
* 借鉴OAuth2实现了多终端认证系统可控制子系统的token权限互相隔离。 * 借鉴OAuth2实现了多终端认证系统可控制子系统的token权限互相隔离。
* 借鉴Security封装了Secure模块采用JWT做Token认证可拓展集成Redis等细颗粒度控制方案。 * 借鉴Security封装了Secure模块采用JWT做Token认证可拓展集成Redis等细颗粒度控制方案。
* 稳定生产了一年,经历了从Camden -> Greenwich的技术架构也经历了从fat jar -> docker -> k8s + jenkins的部署架构 * 稳定生产了一年,经历了从 Camden -> Hoxton -> 2020 的技术架构也经历了从fat jar -> docker -> k8s + jenkins的部署架构
* 项目分包明确,规范微服务的开发模式,使包与包之间的分工清晰。 * 项目分包明确,规范微服务的开发模式,使包与包之间的分工清晰。
## 架构图 ## 架构图

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
<artifactId>blade-tool</artifactId> <artifactId>blade-tool</artifactId>
<version>2.8.0</version> <version>3.0.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -30,7 +30,7 @@ import org.springframework.context.annotation.EnableAspectJAutoProxy;
* @author Chill * @author Chill
*/ */
@Slf4j @Slf4j
@Configuration @Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ @EnableConfigurationProperties({
BladeProperties.class BladeProperties.class
}) })

View File

@ -31,7 +31,7 @@ import java.util.List;
* @author Chill * @author Chill
*/ */
@Slf4j @Slf4j
@Configuration @Configuration(proxyBeanMethods = false)
@EnableCaching @EnableCaching
@Order(Ordered.HIGHEST_PRECEDENCE) @Order(Ordered.HIGHEST_PRECEDENCE)
public class BladeWebMvcConfiguration implements WebMvcConfigurer { public class BladeWebMvcConfiguration implements WebMvcConfigurer {

View File

@ -33,7 +33,7 @@ import org.springframework.context.annotation.Configuration;
* *
* @author Chill * @author Chill
*/ */
@Configuration @Configuration(proxyBeanMethods = false)
@AllArgsConstructor @AllArgsConstructor
@MapperScan("org.springblade.**.mapper.**") @MapperScan("org.springblade.**.mapper.**")
public class MybatisPlusConfiguration { public class MybatisPlusConfiguration {

View File

@ -28,7 +28,7 @@ import org.springframework.retry.interceptor.RetryOperationsInterceptor;
* @author Chill * @author Chill
*/ */
@Slf4j @Slf4j
@Configuration @Configuration(proxyBeanMethods = false)
public class RetryConfiguration { public class RetryConfiguration {
@Bean @Bean

View File

@ -36,7 +36,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
*/ */
@Slf4j @Slf4j
@Aspect @Aspect
@Configuration @Configuration(proxyBeanMethods = false)
@Profile({AppConstant.DEV_CODE, AppConstant.TEST_CODE}) @Profile({AppConstant.DEV_CODE, AppConstant.TEST_CODE})
public class RequestLogAspect { public class RequestLogAspect {

View File

@ -29,7 +29,7 @@ import org.springframework.context.annotation.Configuration;
* *
* @author Chill * @author Chill
*/ */
@Configuration @Configuration(proxyBeanMethods = false)
@AllArgsConstructor @AllArgsConstructor
@AutoConfigureBefore(MybatisPlusConfiguration.class) @AutoConfigureBefore(MybatisPlusConfiguration.class)
@EnableConfigurationProperties(BladeTenantProperties.class) @EnableConfigurationProperties(BladeTenantProperties.class)

View File

@ -97,7 +97,7 @@ mybatis-plus:
swagger: swagger:
title: SpringBlade 接口文档系统 title: SpringBlade 接口文档系统
description: SpringBlade 接口文档系统 description: SpringBlade 接口文档系统
version: 2.8.0 version: 3.0.0
license: Powered By SpringBlade license: Powered By SpringBlade
licenseUrl: https://bladex.vip licenseUrl: https://bladex.vip
terms-of-service-url: https://bladex.vip terms-of-service-url: https://bladex.vip

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>blade-tool</artifactId> <artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
<version>2.8.0</version> <version>3.0.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@ -22,42 +22,22 @@
<artifactId>blade-core-secure</artifactId> <artifactId>blade-core-secure</artifactId>
<version>${blade.tool.version}</version> <version>${blade.tool.version}</version>
</dependency> </dependency>
<!--Spring-->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<!--Feign--> <!--Feign-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>10.4.0</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId> <artifactId>spring-cloud-starter-openfeign</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<!--Hystrix--> <dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId> <artifactId>spring-cloud-starter-loadbalancer</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<!-- Actuator -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-boot-starter-actuator</artifactId> <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency> </dependency>
<!-- Admin --> <!-- Admin -->
<dependency> <dependency>

View File

@ -0,0 +1,42 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* 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
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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.client;
import org.springblade.core.launch.constant.AppConstant;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
import org.springframework.cloud.openfeign.EnableFeignClients;
import java.lang.annotation.*;
/**
* Cloud启动注解配置
*
* @author Chill
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@EnableDiscoveryClient
@EnableCircuitBreaker
@EnableFeignClients(AppConstant.BASE_PACKAGES)
@SpringBootApplication(exclude = RibbonAutoConfiguration.class)
public @interface BladeCloudApplication {
}

View File

@ -0,0 +1,54 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* 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
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 com.alibaba.cloud.sentinel.feign.SentinelFeignAutoConfiguration;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import feign.Feign;
import org.springblade.core.cloud.feign.BladeFeignSentinel;
import org.springblade.core.cloud.sentinel.BladeBlockExceptionHandler;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
/**
* blade cloud 增强配置
*
* @author Chill
*/
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(SentinelFeignAutoConfiguration.class)
public class BladeCloudAutoConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.sentinel.enabled")
public Feign.Builder feignSentinelBuilder() {
return BladeFeignSentinel.builder();
}
@Bean
@ConditionalOnMissingBean
public BlockExceptionHandler blockExceptionHandler() {
return new BladeBlockExceptionHandler();
}
}

View File

@ -16,9 +16,9 @@
package org.springblade.core.cloud.feign; package org.springblade.core.cloud.feign;
import feign.Target; import feign.Target;
import feign.hystrix.FallbackFactory;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cloud.openfeign.FallbackFactory;
/** /**
* 默认 Fallback避免写过多fallback类 * 默认 Fallback避免写过多fallback类

View File

@ -1,96 +0,0 @@
/*
* Copyright 2013-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.HystrixCommand;
import feign.Contract;
import feign.Feign;
import feign.RequestInterceptor;
import feign.hystrix.HystrixFeign;
import org.springblade.core.tool.convert.EnumToStringConverter;
import org.springblade.core.tool.convert.StringToEnumConverter;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.openfeign.BladeFeignClientsRegistrar;
import org.springframework.cloud.openfeign.BladeHystrixTargeter;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.Targeter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Scope;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.ConverterRegistry;
import java.util.ArrayList;
/**
* blade feign 增强配置
*
* @author L.cm
*/
@Configuration
@ConditionalOnClass(Feign.class)
@Import(BladeFeignClientsRegistrar.class)
@AutoConfigureAfter(EnableFeignClients.class)
public class BladeFeignAutoConfiguration {
@Bean
@ConditionalOnMissingBean(Targeter.class)
public Targeter bladeFeignTargeter() {
return new BladeHystrixTargeter();
}
@Configuration("hystrixFeignConfiguration")
@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
protected static class HystrixFeignConfiguration {
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@ConditionalOnProperty("feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder(
RequestInterceptor requestInterceptor, Contract feignContract) {
return HystrixFeign.builder()
.contract(feignContract)
.decode404()
.requestInterceptor(requestInterceptor);
}
@Bean
@ConditionalOnMissingBean
public RequestInterceptor requestInterceptor() {
return new BladeFeignRequestHeaderInterceptor();
}
}
/**
* blade enum - String 转换配置
* @param conversionService ConversionService
* @return SpringMvcContract
*/
@Bean
public Contract feignContract(@Qualifier("mvcConversionService") ConversionService conversionService) {
ConverterRegistry converterRegistry = ((ConverterRegistry) conversionService);
converterRegistry.addConverter(new EnumToStringConverter());
converterRegistry.addConverter(new StringToEnumConverter());
return new BladeSpringMvcContract(new ArrayList<>(), conversionService);
}
}

View File

@ -41,7 +41,7 @@ public class BladeFeignFallback<T> implements MethodInterceptor {
private final Class<T> targetType; private final Class<T> targetType;
private final String targetName; private final String targetName;
private final Throwable cause; private final Throwable cause;
private final String code = "code"; private final static String CODE = "code";
@Nullable @Nullable
@Override @Override
@ -66,7 +66,7 @@ public class BladeFeignFallback<T> implements MethodInterceptor {
// 转换成 jsonNode 读取因为直接转换可能 对方放回的并 不是 R 的格式 // 转换成 jsonNode 读取因为直接转换可能 对方放回的并 不是 R 的格式
JsonNode resultNode = JsonUtil.readTree(content); JsonNode resultNode = JsonUtil.readTree(content);
// 判断是否 R 格式 返回体 // 判断是否 R 格式 返回体
if (resultNode.has(code)) { if (resultNode.has(CODE)) {
return JsonUtil.getInstance().convertValue(resultNode, R.class); return JsonUtil.getInstance().convertValue(resultNode, R.class);
} }
return R.fail(resultNode.toString()); return R.fail(resultNode.toString());

View File

@ -0,0 +1,136 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* 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.alibaba.cloud.sentinel.feign.SentinelContractHolder;
import feign.Contract;
import feign.Feign;
import feign.InvocationHandlerFactory;
import feign.Target;
import lombok.SneakyThrows;
import org.springblade.core.cloud.sentinel.BladeSentinelInvocationHandler;
import org.springframework.beans.BeansException;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.cloud.openfeign.FeignContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
/**
* feign集成sentinel自动配置
* 重写 {@link com.alibaba.cloud.sentinel.feign.SentinelFeign} 适配最新API
*
* @author Chill
*/
public class BladeFeignSentinel {
public static Builder builder() {
return new Builder();
}
public static final class Builder extends Feign.Builder
implements ApplicationContextAware {
private Contract contract = new Contract.Default();
private ApplicationContext applicationContext;
private FeignContext feignContext;
@Override
public Feign.Builder invocationHandlerFactory(
InvocationHandlerFactory invocationHandlerFactory) {
throw new UnsupportedOperationException();
}
@Override
public Builder contract(Contract contract) {
this.contract = contract;
return this;
}
@Override
public Feign build() {
super.invocationHandlerFactory(new InvocationHandlerFactory() {
@SneakyThrows
@Override
public InvocationHandler create(Target target,
Map<Method, MethodHandler> dispatch) {
Object feignClientFactoryBean = Builder.this.applicationContext.getBean("&" + target.type().getName());
Class fallback = (Class) getFieldValue(feignClientFactoryBean, "fallback");
Class fallbackFactory = (Class) getFieldValue(feignClientFactoryBean, "fallbackFactory");
String name = (String) getFieldValue(feignClientFactoryBean, "name");
Object fallbackInstance;
FallbackFactory fallbackFactoryInstance;
// 判断fallback类型
if (void.class != fallback) {
fallbackInstance = getFromContext(name, "fallback", fallback, target.type());
return new BladeSentinelInvocationHandler(target, dispatch, new FallbackFactory.Default(fallbackInstance));
}
if (void.class != fallbackFactory) {
fallbackFactoryInstance = (FallbackFactory) getFromContext(name, "fallbackFactory", fallbackFactory, FallbackFactory.class);
return new BladeSentinelInvocationHandler(target, dispatch, fallbackFactoryInstance);
}
// 默认fallbackFactory
BladeFallbackFactory bladeFallbackFactory = new BladeFallbackFactory(target);
return new BladeSentinelInvocationHandler(target, dispatch, bladeFallbackFactory);
}
private Object getFromContext(String name, String type, Class fallbackType, Class targetType) {
Object fallbackInstance = feignContext.getInstance(name, fallbackType);
if (fallbackInstance == null) {
throw new IllegalStateException(
String.format("No %s instance of type %s found for feign client %s",
type, fallbackType, name)
);
}
if (!targetType.isAssignableFrom(fallbackType)) {
throw new IllegalStateException(
String.format("Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
type, fallbackType, targetType, name)
);
}
return fallbackInstance;
}
});
super.contract(new SentinelContractHolder(contract));
return super.build();
}
private Object getFieldValue(Object instance, String fieldName) {
Field field = ReflectionUtils.findField(instance.getClass(), fieldName);
field.setAccessible(true);
try {
return field.get(instance);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
feignContext = this.applicationContext.getBean(FeignContext.class);
}
}
}

View File

@ -18,6 +18,8 @@ package org.springblade.core.cloud.feign;
import org.springblade.core.launch.constant.AppConstant; import org.springblade.core.launch.constant.AppConstant;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.EnableFeignClients;
import java.lang.annotation.*; import java.lang.annotation.*;
@ -31,6 +33,7 @@ import java.lang.annotation.*;
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@EnableFeignClients(AppConstant.BASE_PACKAGES) @EnableFeignClients(AppConstant.BASE_PACKAGES)
@EnableAutoConfiguration(exclude = RibbonAutoConfiguration.class)
public @interface EnableBladeFeign { public @interface EnableBladeFeign {
/** /**
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springblade.core.cloud.hystrix; package org.springblade.core.cloud.header;
import org.springblade.core.secure.BladeUser; import org.springblade.core.secure.BladeUser;
import org.springblade.core.secure.utils.SecureUtil; import org.springblade.core.secure.utils.SecureUtil;
@ -27,7 +27,7 @@ import javax.servlet.http.HttpServletRequest;
* *
* @author Chill * @author Chill
*/ */
public class BladeAccountGetter implements BladeHystrixAccountGetter { public class BladeAccountGetter implements BladeFeignAccountGetter {
@Override @Override
public String get(HttpServletRequest request) { public String get(HttpServletRequest request) {

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springblade.core.cloud.hystrix; package org.springblade.core.cloud.header;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
@ -25,7 +25,7 @@ import javax.servlet.http.HttpServletRequest;
* *
* @author L.cm * @author L.cm
*/ */
public interface BladeHystrixAccountGetter { public interface BladeFeignAccountGetter {
/** /**
* 账号信息获取器 * 账号信息获取器

View File

@ -13,11 +13,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springblade.core.cloud.feign; package org.springblade.core.cloud.header;
import feign.RequestInterceptor; import feign.RequestInterceptor;
import feign.RequestTemplate; import feign.RequestTemplate;
import org.springblade.core.cloud.hystrix.BladeHttpHeadersContextHolder;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
/** /**

View File

@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springblade.core.cloud.hystrix; package org.springblade.core.cloud.header;
import org.springblade.core.cloud.props.BladeHystrixHeadersProperties; import org.springblade.core.cloud.props.BladeFeignHeadersProperties;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
@ -33,8 +33,8 @@ public class BladeHttpHeadersCallable<V> implements Callable<V> {
private HttpHeaders httpHeaders; private HttpHeaders httpHeaders;
public BladeHttpHeadersCallable(Callable<V> delegate, public BladeHttpHeadersCallable(Callable<V> delegate,
@Nullable BladeHystrixAccountGetter accountGetter, @Nullable BladeFeignAccountGetter accountGetter,
BladeHystrixHeadersProperties properties) { BladeFeignHeadersProperties properties) {
this.delegate = delegate; this.delegate = delegate;
this.httpHeaders = BladeHttpHeadersContextHolder.toHeaders(accountGetter, properties); this.httpHeaders = BladeHttpHeadersContextHolder.toHeaders(accountGetter, properties);
} }

View File

@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springblade.core.cloud.hystrix; package org.springblade.core.cloud.header;
import org.springblade.core.cloud.props.BladeHystrixHeadersProperties; import org.springblade.core.cloud.props.BladeFeignHeadersProperties;
import org.springblade.core.tool.utils.StringUtil; import org.springblade.core.tool.utils.StringUtil;
import org.springblade.core.tool.utils.WebUtil; import org.springblade.core.tool.utils.WebUtil;
import org.springframework.core.NamedThreadLocal; import org.springframework.core.NamedThreadLocal;
@ -35,7 +35,7 @@ import java.util.List;
* @author L.cm * @author L.cm
*/ */
public class BladeHttpHeadersContextHolder { public class BladeHttpHeadersContextHolder {
private static final ThreadLocal<HttpHeaders> HTTP_HEADERS_HOLDER = new NamedThreadLocal<>("Blade hystrix HttpHeaders"); private static final ThreadLocal<HttpHeaders> HTTP_HEADERS_HOLDER = new NamedThreadLocal<>("Blade Feign HttpHeaders");
/** /**
* 请求和转发的ip * 请求和转发的ip
@ -59,8 +59,8 @@ public class BladeHttpHeadersContextHolder {
@Nullable @Nullable
public static HttpHeaders toHeaders( public static HttpHeaders toHeaders(
@Nullable BladeHystrixAccountGetter accountGetter, @Nullable BladeFeignAccountGetter accountGetter,
BladeHystrixHeadersProperties properties) { BladeFeignHeadersProperties properties) {
HttpServletRequest request = WebUtil.getRequest(); HttpServletRequest request = WebUtil.getRequest();
if (request == null) { if (request == null) {
return null; return null;

View File

@ -18,12 +18,13 @@ package org.springblade.core.cloud.http;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.springblade.core.cloud.hystrix.BladeHystrixAccountGetter; import org.springblade.core.cloud.header.BladeFeignAccountGetter;
import org.springblade.core.cloud.props.BladeHystrixHeadersProperties; import org.springblade.core.cloud.props.BladeFeignHeadersProperties;
import org.springblade.core.tool.utils.Charsets; import org.springblade.core.tool.utils.Charsets;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory; import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory;
import org.springframework.cloud.commons.httpclient.OkHttpClientFactory; import org.springframework.cloud.commons.httpclient.OkHttpClientFactory;
@ -47,9 +48,10 @@ import java.util.concurrent.TimeUnit;
* *
* @author L.cm * @author L.cm
*/ */
@Configuration @Configuration(proxyBeanMethods = false)
@ConditionalOnClass(okhttp3.OkHttpClient.class)
@AllArgsConstructor @AllArgsConstructor
@ConditionalOnClass(okhttp3.OkHttpClient.class)
@EnableConfigurationProperties(BladeFeignHeadersProperties.class)
public class RestTemplateConfiguration { public class RestTemplateConfiguration {
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
@ -135,8 +137,8 @@ public class RestTemplateConfiguration {
@Bean @Bean
public RestTemplateHeaderInterceptor requestHeaderInterceptor( public RestTemplateHeaderInterceptor requestHeaderInterceptor(
@Autowired(required = false) @Nullable BladeHystrixAccountGetter accountGetter, @Autowired(required = false) @Nullable BladeFeignAccountGetter accountGetter,
BladeHystrixHeadersProperties properties) { BladeFeignHeadersProperties properties) {
return new RestTemplateHeaderInterceptor(accountGetter,properties); return new RestTemplateHeaderInterceptor(accountGetter,properties);
} }

View File

@ -16,9 +16,9 @@
package org.springblade.core.cloud.http; package org.springblade.core.cloud.http;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.springblade.core.cloud.hystrix.BladeHttpHeadersContextHolder; import org.springblade.core.cloud.header.BladeHttpHeadersContextHolder;
import org.springblade.core.cloud.hystrix.BladeHystrixAccountGetter; import org.springblade.core.cloud.header.BladeFeignAccountGetter;
import org.springblade.core.cloud.props.BladeHystrixHeadersProperties; import org.springblade.core.cloud.props.BladeFeignHeadersProperties;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest; import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestExecution;
@ -36,8 +36,8 @@ import java.io.IOException;
@AllArgsConstructor @AllArgsConstructor
public class RestTemplateHeaderInterceptor implements ClientHttpRequestInterceptor { public class RestTemplateHeaderInterceptor implements ClientHttpRequestInterceptor {
@Nullable @Nullable
private final BladeHystrixAccountGetter accountGetter; private final BladeFeignAccountGetter accountGetter;
private final BladeHystrixHeadersProperties properties; private final BladeFeignHeadersProperties properties;
@Override @Override
public ClientHttpResponse intercept( public ClientHttpResponse intercept(

View File

@ -1,71 +0,0 @@
/**
* Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
* <p>
* 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
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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);
}
}

View File

@ -1,93 +0,0 @@
/**
* Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
* <p>
* 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
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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中的一些变量
*
* <p>
* 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
* </p>
*
* @author L.cm
*/
@AllArgsConstructor
public class BladeHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {
@Nullable
private final HystrixConcurrencyStrategy existingConcurrencyStrategy;
@Nullable
private final BladeHystrixAccountGetter accountGetter;
private final BladeHystrixHeadersProperties properties;
@Override
public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
return existingConcurrencyStrategy != null
? existingConcurrencyStrategy.getBlockingQueue(maxQueueSize)
: super.getBlockingQueue(maxQueueSize);
}
@Override
public <T> HystrixRequestVariable<T> getRequestVariable(
HystrixRequestVariableLifecycle<T> rv) {
return existingConcurrencyStrategy != null
? existingConcurrencyStrategy.getRequestVariable(rv)
: super.getRequestVariable(rv);
}
@Override
public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
HystrixProperty<Integer> corePoolSize,
HystrixProperty<Integer> maximumPoolSize,
HystrixProperty<Integer> keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
return existingConcurrencyStrategy != null
? existingConcurrencyStrategy.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue)
: super.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties threadPoolProperties) {
return existingConcurrencyStrategy != null
? existingConcurrencyStrategy.getThreadPool(threadPoolKey, threadPoolProperties)
: super.getThreadPool(threadPoolKey, threadPoolProperties);
}
@Override
public <T> Callable<T> wrapCallable(Callable<T> callable) {
Callable<T> wrapCallable = new BladeHttpHeadersCallable<>(callable, accountGetter, properties);
return existingConcurrencyStrategy != null
? existingConcurrencyStrategy.wrapCallable(wrapCallable)
: super.wrapCallable(wrapCallable);
}
}

View File

@ -32,8 +32,8 @@ import java.util.List;
@Getter @Getter
@Setter @Setter
@RefreshScope @RefreshScope
@ConfigurationProperties("blade.hystrix.headers") @ConfigurationProperties("blade.feign.headers")
public class BladeHystrixHeadersProperties { public class BladeFeignHeadersProperties {
/** /**
* 用于 聚合层 向调用层传递用户信息 的请求头默认x-blade-account * 用于 聚合层 向调用层传递用户信息 的请求头默认x-blade-account

View File

@ -0,0 +1,26 @@
package org.springblade.core.cloud.sentinel;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Sentinel统一限流策略
*
* @author Chill
*/
public class BladeBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
// Return 429 (Too Many Requests) by default.
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getWriter().print(JsonUtil.toJson(R.fail(e.getMessage())));
}
}

View File

@ -0,0 +1,168 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* 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
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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.sentinel;
import com.alibaba.cloud.sentinel.feign.SentinelContractHolder;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import feign.Feign;
import feign.InvocationHandlerFactory;
import feign.MethodMetadata;
import feign.Target;
import org.springframework.cloud.openfeign.FallbackFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.LinkedHashMap;
import java.util.Map;
import static feign.Util.checkNotNull;
/**
* 重写SentinelInvocationHandler适配最新API
*
* @author Chill
*/
public class BladeSentinelInvocationHandler implements InvocationHandler {
private final Target<?> target;
private final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch;
private FallbackFactory fallbackFactory;
private Map<Method, Method> fallbackMethodMap;
public BladeSentinelInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch,
FallbackFactory fallbackFactory) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch");
this.fallbackFactory = fallbackFactory;
this.fallbackMethodMap = toFallbackMethod(dispatch);
}
public BladeSentinelInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch");
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
if ("equals".equals(method.getName())) {
try {
Object otherHandler = args.length > 0 && args[0] != null
? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
Object result;
InvocationHandlerFactory.MethodHandler methodHandler = this.dispatch.get(method);
// only handle by HardCodedTarget
if (target instanceof Target.HardCodedTarget) {
Target.HardCodedTarget hardCodedTarget = (Target.HardCodedTarget) target;
MethodMetadata methodMetadata = SentinelContractHolder.METADATA_MAP
.get(hardCodedTarget.type().getName()
+ Feign.configKey(hardCodedTarget.type(), method));
// resource default is HttpMethod:protocol://url
if (methodMetadata == null) {
result = methodHandler.invoke(args);
} else {
String resourceName = methodMetadata.template().method().toUpperCase()
+ ":" + hardCodedTarget.url() + methodMetadata.template().path();
Entry entry = null;
try {
ContextUtil.enter(resourceName);
entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
result = methodHandler.invoke(args);
} catch (Throwable ex) {
// fallback handle
if (!BlockException.isBlockException(ex)) {
Tracer.trace(ex);
}
if (fallbackFactory != null) {
try {
Object fallbackResult = fallbackMethodMap.get(method)
.invoke(fallbackFactory.create(ex), args);
return fallbackResult;
} catch (IllegalAccessException e) {
// shouldn't happen as method is public due to being an
// interface
throw new AssertionError(e);
} catch (InvocationTargetException e) {
throw new AssertionError(e.getCause());
}
} else {
// throw exception if fallbackFactory is null
throw ex;
}
} finally {
if (entry != null) {
entry.exit(1, args);
}
ContextUtil.exit();
}
}
} else {
// other target type using default strategy
result = methodHandler.invoke(args);
}
return result;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof BladeSentinelInvocationHandler) {
BladeSentinelInvocationHandler other = (BladeSentinelInvocationHandler) obj;
return target.equals(other.target);
}
return false;
}
@Override
public int hashCode() {
return target.hashCode();
}
@Override
public String toString() {
return target.toString();
}
static Map<Method, Method> toFallbackMethod(Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
Map<Method, Method> result = new LinkedHashMap<>();
for (Method method : dispatch.keySet()) {
method.setAccessible(true);
result.put(method, method);
}
return result;
}
}

View File

@ -13,12 +13,11 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springblade.core.cloud.feign; package org.springblade.core.cloud.version;
import feign.MethodMetadata; import feign.MethodMetadata;
import org.springblade.core.cloud.annotation.ApiVersion; import org.springblade.core.cloud.annotation.ApiVersion;
import org.springblade.core.cloud.annotation.UrlVersion; 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.StringPool;
import org.springblade.core.tool.utils.StringUtil; import org.springblade.core.tool.utils.StringUtil;
import org.springframework.cloud.openfeign.AnnotatedParameterProcessor; import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;

View File

@ -27,7 +27,7 @@ import org.springframework.context.annotation.Configuration;
* *
* @author L.cm * @author L.cm
*/ */
@Configuration @Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication @ConditionalOnWebApplication
public class VersionMappingAutoConfiguration { public class VersionMappingAutoConfiguration {
@Bean @Bean

View File

@ -1,230 +0,0 @@
/**
* Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
* <p>
* 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
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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<String> feignClients = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
// 如果 spring.factories 里为空
if (feignClients.isEmpty()) {
return;
}
for (String className : feignClients) {
try {
Class<?> clazz = beanClassLoader.loadClass(className);
AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(clazz, FeignClient.class);
if (attributes == null) {
continue;
}
// 如果已经存在该 bean支持原生的 Feign
if (registry.containsBeanDefinition(className)) {
continue;
}
registerClientConfiguration(registry, getClientName(attributes), attributes.get("configuration"));
validate(attributes);
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
// 兼容最新版本的 spring-cloud-openfeign尚未发布
StringBuilder aliasBuilder = new StringBuilder(18);
if (attributes.containsKey("contextId")) {
String contextId = getContextId(attributes);
aliasBuilder.append(contextId);
definition.addPropertyValue("contextId", contextId);
} else {
aliasBuilder.append(name);
}
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
// alias
String alias = aliasBuilder.append("FeignClient").toString();
// has a default, won't be null
boolean primary = (Boolean)attributes.get("primary");
beanDefinition.setPrimary(primary);
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
/**
* Return the class used by {@link SpringFactoriesLoader} to load configuration
* candidates.
* @return the factory class
*/
private Class<?> getSpringFactoriesLoaderFactoryClass() {
return BladeFeignAutoConfiguration.class;
}
private void validate(Map<String, Object> attributes) {
AnnotationAttributes annotation = AnnotationAttributes.fromMap(attributes);
// This blows up if an aliased property is overspecified
// FIXME annotation.getAliasedString("name", FeignClient.class, null);
FeignClientsRegistrar.validateFallback(annotation.getClass("fallback"));
FeignClientsRegistrar.validateFallbackFactory(annotation.getClass("fallbackFactory"));
}
private String getName(Map<String, Object> attributes) {
String name = (String) attributes.get("serviceId");
if (!StringUtils.hasText(name)) {
name = (String) attributes.get("name");
}
if (!StringUtils.hasText(name)) {
name = (String) attributes.get("value");
}
name = resolve(name);
return FeignClientsRegistrar.getName(name);
}
private String getContextId(Map<String, Object> attributes) {
String contextId = (String) attributes.get("contextId");
if (!StringUtils.hasText(contextId)) {
return getName(attributes);
}
contextId = resolve(contextId);
return FeignClientsRegistrar.getName(contextId);
}
private String resolve(String value) {
if (StringUtils.hasText(value)) {
return this.environment.resolvePlaceholders(value);
}
return value;
}
private String getUrl(Map<String, Object> attributes) {
String url = resolve((String) attributes.get("url"));
return FeignClientsRegistrar.getUrl(url);
}
private String getPath(Map<String, Object> attributes) {
String path = resolve((String) attributes.get("path"));
return FeignClientsRegistrar.getPath(path);
}
@Nullable
private String getQualifier(@Nullable Map<String, Object> client) {
if (client == null) {
return null;
}
String qualifier = (String) client.get("qualifier");
if (StringUtils.hasText(qualifier)) {
return qualifier;
}
return null;
}
@Nullable
private String getClientName(@Nullable Map<String, Object> client) {
if (client == null) {
return null;
}
String value = (String) client.get("contextId");
if (!StringUtils.hasText(value)) {
value = (String) client.get("value");
}
if (!StringUtils.hasText(value)) {
value = (String) client.get("name");
}
if (!StringUtils.hasText(value)) {
value = (String) client.get("serviceId");
}
if (StringUtils.hasText(value)) {
return value;
}
throw new IllegalStateException("Either 'name' or 'value' must be provided in @" + FeignClient.class.getSimpleName());
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(), builder.getBeanDefinition());
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}

View File

@ -1,99 +0,0 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 feign.Feign;
import feign.Target;
import feign.hystrix.FallbackFactory;
import feign.hystrix.HystrixFeign;
import feign.hystrix.SetterFactory;
import org.springblade.core.cloud.feign.BladeFallbackFactory;
import org.springframework.lang.Nullable;
/**
* 添加 blade 默认的 fallbackFactory L.cm 2019.01.19
*
* @author L.cm
* @author Spencer Gibb
* @author Erik Kringen
*/
@SuppressWarnings("unchecked")
public class BladeHystrixTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget<T> target) {
if (!(feign instanceof HystrixFeign.Builder)) {
return feign.target(target);
}
HystrixFeign.Builder builder = (HystrixFeign.Builder) feign;
SetterFactory setterFactory = getOptional(factory.getName(), context, SetterFactory.class);
if (setterFactory != null) {
builder.setterFactory(setterFactory);
}
Class<?> fallback = factory.getFallback();
if (fallback != void.class) {
return targetWithFallback(factory.getName(), context, target, builder, fallback);
}
Class<?> fallbackFactory = factory.getFallbackFactory();
if (fallbackFactory != void.class) {
return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);
}
// blade 默认的 fallbackFactory
BladeFallbackFactory bladeFallbackFactory = new BladeFallbackFactory(target);
return (T) builder.target(target, bladeFallbackFactory);
}
private <T> T targetWithFallbackFactory(String feignClientName, FeignContext context,
Target.HardCodedTarget<T> target,
HystrixFeign.Builder builder,
Class<?> fallbackFactoryClass) {
FallbackFactory<? extends T> fallbackFactory = (FallbackFactory<? extends T>)
getFromContext("fallbackFactory", feignClientName, context, fallbackFactoryClass, FallbackFactory.class);
return builder.target(target, fallbackFactory);
}
private <T> T targetWithFallback(String feignClientName, FeignContext context,
Target.HardCodedTarget<T> target,
HystrixFeign.Builder builder, Class<?> fallback) {
T fallbackInstance = getFromContext("fallback", feignClientName, context, fallback, target.type());
return builder.target(target, fallbackInstance);
}
private <T> T getFromContext(String fallbackMechanism, String feignClientName, FeignContext context, Class<?> beanType,
Class<T> targetType) {
Object fallbackInstance = context.getInstance(feignClientName, beanType);
if (fallbackInstance == null) {
throw new IllegalStateException(String.format("No " + fallbackMechanism +
" instance of type %s found for feign client %s", beanType, feignClientName));
}
if (!targetType.isAssignableFrom(beanType)) {
throw new IllegalStateException(String.format(
"Incompatible " + fallbackMechanism + " instance. Fallback/fallbackFactory of " +
"type %s is not assignable to %s for feign client %s", beanType, targetType, feignClientName));
}
return (T) fallbackInstance;
}
@Nullable
private <T> T getOptional(String feignClientName, FeignContext context, Class<T> beanType) {
return context.getInstance(feignClientName, beanType);
}
}

View File

@ -1,39 +0,0 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 feign.Feign;
import feign.Target;
/**
* @author Spencer Gibb
*/
public interface Targeter {
/**
* target
*
* @param factory
* @param feign
* @param context
* @param target
* @param <T>
* @return T
*/
<T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget<T> target);
}

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>blade-tool</artifactId> <artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
<version>2.8.0</version> <version>3.0.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>blade-tool</artifactId> <artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
<version>2.8.0</version> <version>3.0.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -30,7 +30,7 @@ import org.springframework.util.StringUtils;
* @author Chill * @author Chill
*/ */
@Slf4j @Slf4j
@Configuration @Configuration(proxyBeanMethods = false)
public class StartEventListener { public class StartEventListener {
@Async @Async

View File

@ -27,7 +27,7 @@ import org.springframework.core.annotation.Order;
* *
* @author Chill * @author Chill
*/ */
@Configuration @Configuration(proxyBeanMethods = false)
@AllArgsConstructor @AllArgsConstructor
@Order(Ordered.HIGHEST_PRECEDENCE) @Order(Ordered.HIGHEST_PRECEDENCE)
@EnableConfigurationProperties({ @EnableConfigurationProperties({

View File

@ -25,7 +25,7 @@ public interface AppConstant {
/** /**
* 应用版本 * 应用版本
*/ */
String APPLICATION_VERSION = "2.8.0"; String APPLICATION_VERSION = "3.0.0";
/** /**
* 基础包 * 基础包

View File

@ -28,7 +28,7 @@ import org.springframework.context.annotation.Configuration;
* @author Chill * @author Chill
*/ */
@Getter @Getter
@Configuration @Configuration(proxyBeanMethods = false)
public class ServerInfo implements SmartInitializingSingleton { public class ServerInfo implements SmartInitializingSingleton {
private final ServerProperties serverProperties; private final ServerProperties serverProperties;
private String hostName; private String hostName;

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>blade-tool</artifactId> <artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
<version>2.8.0</version> <version>3.0.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -41,7 +41,7 @@ import javax.servlet.Servlet;
* *
* @author Chill * @author Chill
*/ */
@Configuration @Configuration(proxyBeanMethods = false)
@AllArgsConstructor @AllArgsConstructor
@ConditionalOnWebApplication @ConditionalOnWebApplication
@AutoConfigureBefore(ErrorMvcAutoConfiguration.class) @AutoConfigureBefore(ErrorMvcAutoConfiguration.class)

View File

@ -34,7 +34,7 @@ import org.springframework.context.annotation.Configuration;
* *
* @author Chill * @author Chill
*/ */
@Configuration @Configuration(proxyBeanMethods = false)
@AllArgsConstructor @AllArgsConstructor
@ConditionalOnWebApplication @ConditionalOnWebApplication
public class BladeLogToolAutoConfiguration { public class BladeLogToolAutoConfiguration {

View File

@ -18,6 +18,7 @@ package org.springblade.core.log.error;
import org.springblade.core.tool.jackson.JsonUtil; import org.springblade.core.tool.jackson.JsonUtil;
import org.springframework.boot.autoconfigure.web.ErrorProperties; import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController; import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.servlet.error.ErrorAttributes; import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@ -41,12 +42,13 @@ public class BladeErrorController extends BasicErrorController {
@Override @Override
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL)); boolean includeStackTrace = isIncludeStackTrace(request, MediaType.ALL);
Map<String, Object> body = getErrorAttributes(request, (includeStackTrace) ? ErrorAttributeOptions.of(ErrorAttributeOptions.Include.STACK_TRACE) : ErrorAttributeOptions.defaults());
HttpStatus status = getStatus(request); HttpStatus status = getStatus(request);
response.setStatus(status.value()); response.setStatus(status.value());
MappingJackson2JsonView view = new MappingJackson2JsonView(); MappingJackson2JsonView view = new MappingJackson2JsonView();
view.setObjectMapper(JsonUtil.getInstance()); view.setObjectMapper(JsonUtil.getInstance());
view.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); view.setContentType(MediaType.APPLICATION_JSON_VALUE);
return new ModelAndView(view, body); return new ModelAndView(view, body);
} }

View File

@ -55,7 +55,7 @@ import java.util.Set;
* @author Chill * @author Chill
*/ */
@Slf4j @Slf4j
@Configuration @Configuration(proxyBeanMethods = false)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class}) @ConditionalOnClass({Servlet.class, DispatcherServlet.class})
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@RestControllerAdvice @RestControllerAdvice

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>blade-tool</artifactId> <artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
<version>2.8.0</version> <version>3.0.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>blade-tool</artifactId> <artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
<version>2.8.0</version> <version>3.0.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -37,7 +37,7 @@ import org.springframework.context.annotation.Configuration;
* *
* @author Chill * @author Chill
*/ */
@Configuration @Configuration(proxyBeanMethods = false)
@AllArgsConstructor @AllArgsConstructor
@AutoConfigureAfter(QiniuConfiguration.class) @AutoConfigureAfter(QiniuConfiguration.class)
@EnableConfigurationProperties(OssProperties.class) @EnableConfigurationProperties(OssProperties.class)

View File

@ -36,7 +36,7 @@ import org.springframework.context.annotation.Configuration;
* *
* @author Chill * @author Chill
*/ */
@Configuration @Configuration(proxyBeanMethods = false)
@AllArgsConstructor @AllArgsConstructor
@EnableConfigurationProperties(OssProperties.class) @EnableConfigurationProperties(OssProperties.class)
@ConditionalOnProperty(value = "oss.name", havingValue = "qiniu") @ConditionalOnProperty(value = "oss.name", havingValue = "qiniu")

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>blade-tool</artifactId> <artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
<version>2.8.0</version> <version>3.0.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -40,7 +40,7 @@ import javax.servlet.Servlet;
* @author Chill * @author Chill
*/ */
@Order @Order
@Configuration @Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(value = "report.enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnProperty(value = "report.enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties({ReportProperties.class, ReportDatabaseProperties.class}) @EnableConfigurationProperties({ReportProperties.class, ReportDatabaseProperties.class})
@ImportResource("classpath:ureport-console-context.xml") @ImportResource("classpath:ureport-console-context.xml")

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>blade-tool</artifactId> <artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
<version>2.8.0</version> <version>3.0.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -29,7 +29,7 @@ import org.springframework.core.annotation.Order;
* @author Chill * @author Chill
*/ */
@Order @Order
@Configuration @Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(SecureConfiguration.class) @AutoConfigureBefore(SecureConfiguration.class)
public class RegistryConfiguration { public class RegistryConfiguration {

View File

@ -39,7 +39,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
* @author Chill * @author Chill
*/ */
@Order @Order
@Configuration @Configuration(proxyBeanMethods = false)
@AllArgsConstructor @AllArgsConstructor
@EnableConfigurationProperties({BladeSecureProperties.class}) @EnableConfigurationProperties({BladeSecureProperties.class})
public class SecureConfiguration implements WebMvcConfigurer { public class SecureConfiguration implements WebMvcConfigurer {

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>blade-tool</artifactId> <artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
<version>2.8.0</version> <version>3.0.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -29,7 +29,7 @@ import org.springframework.context.annotation.Configuration;
* *
* @author Chill * @author Chill
*/ */
@Configuration @Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SocialProperties.class) @EnableConfigurationProperties(SocialProperties.class)
public class SocialConfiguration { public class SocialConfiguration {

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>blade-tool</artifactId> <artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
<version>2.8.0</version> <version>3.0.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -41,7 +41,7 @@ import java.util.List;
* *
* @author Chill * @author Chill
*/ */
@Configuration @Configuration(proxyBeanMethods = false)
@EnableSwagger @EnableSwagger
@EnableConfigurationProperties(SwaggerProperties.class) @EnableConfigurationProperties(SwaggerProperties.class)
@Import(BeanValidatorPluginsConfiguration.class) @Import(BeanValidatorPluginsConfiguration.class)

View File

@ -55,7 +55,7 @@ public class SwaggerProperties {
/** /**
* 版本 * 版本
**/ **/
private String version = "2.8.0"; private String version = "3.0.0";
/** /**
* 许可证 * 许可证
**/ **/

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
<artifactId>blade-tool</artifactId> <artifactId>blade-tool</artifactId>
<version>2.8.0</version> <version>3.0.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -1,30 +0,0 @@
/**
* Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
* <p>
* 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
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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.test;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
/**
* boot test 基类
*
* @author L.cm
*/
@RunWith(BladeSpringRunner.class)
public abstract class BladeBaseTest extends AbstractJUnit4SpringContextTests {
}

View File

@ -16,6 +16,7 @@
package org.springblade.core.test; package org.springblade.core.test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.annotation.AliasFor; import org.springframework.core.annotation.AliasFor;
@ -31,6 +32,7 @@ import java.lang.annotation.*;
@Documented @Documented
@Inherited @Inherited
@SpringBootTest @SpringBootTest
@ExtendWith(BladeSpringExtension.class)
public @interface BladeBootTest { public @interface BladeBootTest {
/** /**
* 服务名appName * 服务名appName

View File

@ -17,7 +17,7 @@
package org.springblade.core.test; package org.springblade.core.test;
import org.junit.runners.model.InitializationError; import org.junit.jupiter.api.extension.ExtensionContext;
import org.springblade.core.launch.BladeApplication; import org.springblade.core.launch.BladeApplication;
import org.springblade.core.launch.constant.AppConstant; import org.springblade.core.launch.constant.AppConstant;
import org.springblade.core.launch.constant.NacosConstant; import org.springblade.core.launch.constant.NacosConstant;
@ -25,7 +25,8 @@ import org.springblade.core.launch.constant.SentinelConstant;
import org.springblade.core.launch.service.LauncherService; import org.springblade.core.launch.service.LauncherService;
import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.lang.NonNull;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -35,14 +36,16 @@ import java.util.stream.Collectors;
* *
* @author L.cm * @author L.cm
*/ */
public class BladeSpringRunner extends SpringJUnit4ClassRunner { public class BladeSpringExtension extends SpringExtension {
public BladeSpringRunner(Class<?> clazz) throws InitializationError { @Override
super(clazz); public void beforeAll(@NonNull ExtensionContext context) throws Exception {
setUpTestClass(clazz); super.beforeAll(context);
setUpTestClass(context);
} }
private void setUpTestClass(Class<?> clazz) { private void setUpTestClass(ExtensionContext context) {
Class<?> clazz = context.getRequiredTestClass();
BladeBootTest bladeBootTest = AnnotationUtils.getAnnotation(clazz, BladeBootTest.class); BladeBootTest bladeBootTest = AnnotationUtils.getAnnotation(clazz, BladeBootTest.class);
if (bladeBootTest == null) { if (bladeBootTest == null) {
throw new BladeBootTestException(String.format("%s must be @BladeBootTest .", clazz)); throw new BladeBootTestException(String.format("%s must be @BladeBootTest .", clazz));
@ -74,7 +77,7 @@ public class BladeSpringRunner extends SpringJUnit4ClassRunner {
launcherList.stream().sorted(Comparator.comparing(LauncherService::getOrder)).collect(Collectors.toList()) launcherList.stream().sorted(Comparator.comparing(LauncherService::getOrder)).collect(Collectors.toList())
.forEach(launcherService -> launcherService.launcher(builder, appName, profile)); .forEach(launcherService -> launcherService.launcher(builder, appName, profile));
} }
System.err.println(String.format("---[junit.test]:[%s]---启动中,读取到的环境变量:[%s]", appName, profile)); System.err.printf("---[junit.test]:[%s]---启动中,读取到的环境变量:[%s]%n", appName, profile);
} }
} }

View File

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
<artifactId>blade-tool</artifactId> <artifactId>blade-tool</artifactId>
<version>2.8.0</version> <version>3.0.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -41,7 +41,7 @@ import java.util.TimeZone;
* *
* @author Chill * @author Chill
*/ */
@Configuration @Configuration(proxyBeanMethods = false)
@AllArgsConstructor @AllArgsConstructor
@ConditionalOnClass(ObjectMapper.class) @ConditionalOnClass(ObjectMapper.class)
@AutoConfigureBefore(JacksonAutoConfiguration.class) @AutoConfigureBefore(JacksonAutoConfiguration.class)

View File

@ -34,7 +34,7 @@ import java.util.List;
* *
* @author Chill * @author Chill
*/ */
@Configuration @Configuration(proxyBeanMethods = false)
@AllArgsConstructor @AllArgsConstructor
@Order(Ordered.HIGHEST_PRECEDENCE) @Order(Ordered.HIGHEST_PRECEDENCE)
public class MessageConfiguration implements WebMvcConfigurer { public class MessageConfiguration implements WebMvcConfigurer {

View File

@ -41,7 +41,7 @@ import java.time.Duration;
* @author Chill * @author Chill
*/ */
@EnableCaching @EnableCaching
@Configuration @Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(RedisAutoConfiguration.class) @AutoConfigureBefore(RedisAutoConfiguration.class)
public class RedisTemplateConfiguration { public class RedisTemplateConfiguration {

View File

@ -28,7 +28,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
* *
* @author Chill * @author Chill
*/ */
@Configuration @Configuration(proxyBeanMethods = false)
@Order(Ordered.HIGHEST_PRECEDENCE) @Order(Ordered.HIGHEST_PRECEDENCE)
public class ToolConfiguration implements WebMvcConfigurer { public class ToolConfiguration implements WebMvcConfigurer {

View File

@ -33,7 +33,7 @@ import javax.servlet.DispatcherType;
* *
* @author Chill * @author Chill
*/ */
@Configuration @Configuration(proxyBeanMethods = false)
@AllArgsConstructor @AllArgsConstructor
@ConditionalOnProperty(value = "blade.xss.enabled", havingValue = "true") @ConditionalOnProperty(value = "blade.xss.enabled", havingValue = "true")
@EnableConfigurationProperties({XssProperties.class, XssUrlProperties.class}) @EnableConfigurationProperties({XssProperties.class, XssUrlProperties.class})

View File

@ -3,6 +3,7 @@ package org.springblade.core.tool.utils;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -69,13 +70,12 @@ public class RedisUtil {
* *
* @param key 可以传一个值 或多个 * @param key 可以传一个值 或多个
*/ */
@SuppressWarnings("unchecked")
public void del(String... key) { public void del(String... key) {
if (key != null && key.length > 0) { if (key != null && key.length > 0) {
if (key.length == 1) { if (key.length == 1) {
redisTemplate.delete(key[0]); redisTemplate.delete(key[0]);
} else { } else {
redisTemplate.delete(CollectionUtil.arrayToList(key)); redisTemplate.delete(Arrays.asList(key));
} }
} }
} }

View File

@ -127,7 +127,7 @@ public class WebUtil extends org.springframework.web.util.WebUtils {
* @param result 结果对象 * @param result 结果对象
*/ */
public static void renderJson(HttpServletResponse response, Object result) { public static void renderJson(HttpServletResponse response, Object result) {
renderJson(response, result, MediaType.APPLICATION_JSON_UTF8_VALUE); renderJson(response, result, MediaType.APPLICATION_JSON_VALUE);
} }
/** /**

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>blade-tool</artifactId> <artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
<version>2.8.0</version> <version>3.0.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@ -15,11 +15,17 @@
<packaging>jar</packaging> <packaging>jar</packaging>
<dependencies> <dependencies>
<!-- Blade-->
<dependency> <dependency>
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
<artifactId>blade-core-mybatis</artifactId> <artifactId>blade-core-mybatis</artifactId>
<version>${blade.tool.version}</version> <version>${blade.tool.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-cloud</artifactId>
<version>${blade.tool.version}</version>
</dependency>
<!-- Cloud--> <!-- Cloud-->
<dependency> <dependency>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>

View File

@ -0,0 +1,33 @@
package com.alibaba.cloud.seata.feign;
import feign.Client;
import feign.Request;
import feign.Response;
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient;
import java.io.IOException;
/**
* 重写SeataFeignBlockingLoadBalancerClient以适配最新API
*
* @author Chill
*/
public class SeataFeignBlockingLoadBalancerClient extends FeignBlockingLoadBalancerClient {
public SeataFeignBlockingLoadBalancerClient(Client delegate,
BlockingLoadBalancerClient loadBalancerClient,
SeataFeignObjectWrapper seataFeignObjectWrapper,
LoadBalancerProperties properties,
LoadBalancerClientFactory loadBalancerClientFactory) {
super((Client) seataFeignObjectWrapper.wrap(delegate), loadBalancerClient, properties, loadBalancerClientFactory);
}
@Override
public Response execute(Request request, Request.Options options) throws IOException {
return super.execute(request, options);
}
}

View File

@ -0,0 +1,85 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* 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 com.alibaba.cloud.seata.feign;
import feign.Client;
import feign.Feign;
import feign.Retryer;
import lombok.RequiredArgsConstructor;
import org.springblade.core.cloud.feign.BladeFeignSentinel;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
/**
* 重写SeataFeignClientAutoConfiguration以适配最新API
*
* @author Chill
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Client.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class SeataFeignClientAutoConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnClass(name = "com.alibaba.csp.sentinel.SphU")
@ConditionalOnProperty(name = "feign.sentinel.enabled", havingValue = "true")
Feign.Builder feignSentinelBuilder(BeanFactory beanFactory) {
return BladeFeignSentinel.builder().retryer(Retryer.NEVER_RETRY)
.client(new SeataFeignClient(beanFactory));
}
@Bean
@ConditionalOnMissingBean
@Scope("prototype")
Feign.Builder feignBuilder(BeanFactory beanFactory) {
return SeataFeignBuilder.builder(beanFactory);
}
@Configuration(proxyBeanMethods = false)
@RequiredArgsConstructor
protected static class FeignBeanPostProcessorConfiguration {
private final LoadBalancedRetryFactory loadBalancedRetryFactory;
private final LoadBalancerProperties properties;
private final LoadBalancerClientFactory loadBalancerClientFactory;
@Bean
SeataBeanPostProcessor seataBeanPostProcessor(SeataFeignObjectWrapper seataFeignObjectWrapper) {
return new SeataBeanPostProcessor(seataFeignObjectWrapper);
}
@Bean
SeataContextBeanPostProcessor seataContextBeanPostProcessor(BeanFactory beanFactory) {
return new SeataContextBeanPostProcessor(beanFactory);
}
@Bean
SeataFeignObjectWrapper seataFeignObjectWrapper(BeanFactory beanFactory) {
return new SeataFeignObjectWrapper(beanFactory, loadBalancedRetryFactory, properties, loadBalancerClientFactory);
}
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* 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 com.alibaba.cloud.seata.feign;
import feign.Client;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient;
import org.springframework.cloud.openfeign.loadbalancer.RetryableFeignBlockingLoadBalancerClient;
/**
* 重写SeataFeignObjectWrapper以适配最新API
*
* @author Chill
*/
public class SeataFeignObjectWrapper {
private final BeanFactory beanFactory;
private final LoadBalancedRetryFactory loadBalancedRetryFactory;
private final LoadBalancerProperties properties;
private final LoadBalancerClientFactory loadBalancerClientFactory;
private SpringClientFactory springClientFactory;
SeataFeignObjectWrapper(BeanFactory beanFactory, LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties, LoadBalancerClientFactory loadBalancerClientFactory) {
this.beanFactory = beanFactory;
this.loadBalancedRetryFactory = loadBalancedRetryFactory;
this.properties = properties;
this.loadBalancerClientFactory = loadBalancerClientFactory;
}
Object wrap(Object bean) {
if (bean instanceof Client && !(bean instanceof SeataFeignClient)) {
if (bean instanceof FeignBlockingLoadBalancerClient) {
FeignBlockingLoadBalancerClient client = (FeignBlockingLoadBalancerClient) bean;
return new SeataFeignBlockingLoadBalancerClient(client.getDelegate(),
beanFactory.getBean(BlockingLoadBalancerClient.class), this, properties, loadBalancerClientFactory);
}
if (bean instanceof RetryableFeignBlockingLoadBalancerClient) {
RetryableFeignBlockingLoadBalancerClient client = (RetryableFeignBlockingLoadBalancerClient) bean;
return new SeataRetryableFeignBlockingLoadBalancerClient(client.getDelegate(),
beanFactory.getBean(BlockingLoadBalancerClient.class), this, loadBalancedRetryFactory, properties, loadBalancerClientFactory);
}
return new SeataFeignClient(this.beanFactory, (Client) bean);
}
return bean;
}
SpringClientFactory clientFactory() {
if (this.springClientFactory == null) {
this.springClientFactory = this.beanFactory
.getBean(SpringClientFactory.class);
}
return this.springClientFactory;
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* 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 com.alibaba.cloud.seata.feign;
import feign.Client;
import feign.Request;
import feign.Response;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.cloud.openfeign.loadbalancer.RetryableFeignBlockingLoadBalancerClient;
import java.io.IOException;
/**
* 拓展SeataRetryableFeignBlockingLoadBalancerClient以适配最新API
*
* @author Chill
*/
public class SeataRetryableFeignBlockingLoadBalancerClient extends RetryableFeignBlockingLoadBalancerClient {
public SeataRetryableFeignBlockingLoadBalancerClient(Client delegate,
BlockingLoadBalancerClient loadBalancerClient,
SeataFeignObjectWrapper seataFeignObjectWrapper,
LoadBalancedRetryFactory loadBalancedRetryFactory,
LoadBalancerProperties properties,
LoadBalancerClientFactory loadBalancerClientFactory) {
super((Client) seataFeignObjectWrapper.wrap(delegate), loadBalancerClient, loadBalancedRetryFactory, properties, loadBalancerClientFactory);
}
@Override
public Response execute(Request request, Request.Options options) throws IOException {
return super.execute(request, options);
}
}

View File

@ -1,5 +1,5 @@
/** /**
* Copyright (c) 2018-2028, lengleng (wangiegie@gmail.com). * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p> * <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -19,6 +19,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
import java.lang.annotation.*; import java.lang.annotation.*;
@ -33,7 +34,7 @@ import java.lang.annotation.*;
@Inherited @Inherited
@EnableDiscoveryClient @EnableDiscoveryClient
@EnableCircuitBreaker @EnableCircuitBreaker
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, RibbonAutoConfiguration.class})
public @interface SeataCloudApplication { public @interface SeataCloudApplication {
} }

View File

@ -1,5 +1,5 @@
/** /**
* Copyright (c) 2018-2028, lengleng (wangiegie@gmail.com). * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p> * <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,7 +22,7 @@ import org.springframework.context.annotation.Configuration;
* *
* @author Chill * @author Chill
*/ */
@Configuration @Configuration(proxyBeanMethods = false)
public class DataSourceConfiguration { public class DataSourceConfiguration {
} }

21
pom.xml
View File

@ -5,7 +5,7 @@
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
<artifactId>blade-tool</artifactId> <artifactId>blade-tool</artifactId>
<version>2.8.0</version> <version>3.0.0</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>blade-tool</name> <name>blade-tool</name>
<description> <description>
@ -36,24 +36,24 @@
</scm> </scm>
<properties> <properties>
<blade.tool.version>2.8.0</blade.tool.version> <blade.tool.version>3.0.0</blade.tool.version>
<java.version>1.8</java.version> <java.version>1.8</java.version>
<maven.plugin.version>3.8.0</maven.plugin.version> <maven.plugin.version>3.8.0</maven.plugin.version>
<swagger.version>2.10.5</swagger.version> <swagger.version>2.10.5</swagger.version>
<swagger.models.version>1.6.2</swagger.models.version> <swagger.models.version>1.6.2</swagger.models.version>
<knife4j.version>2.0.6</knife4j.version> <knife4j.version>2.0.8</knife4j.version>
<mybatis.plus.version>3.4.0</mybatis.plus.version> <mybatis.plus.version>3.4.1</mybatis.plus.version>
<protostuff.version>1.6.0</protostuff.version> <protostuff.version>1.6.0</protostuff.version>
<disruptor.version>3.4.2</disruptor.version> <disruptor.version>3.4.2</disruptor.version>
<spring.boot.admin.version>2.3.0</spring.boot.admin.version> <spring.boot.admin.version>2.3.1</spring.boot.admin.version>
<mica.auto.version>1.2.5</mica.auto.version> <mica.auto.version>1.2.5</mica.auto.version>
<alibaba.cloud.version>2.2.3.RELEASE</alibaba.cloud.version> <alibaba.cloud.version>2.2.3.RELEASE</alibaba.cloud.version>
<alibaba.seata.version>1.3.0</alibaba.seata.version> <alibaba.seata.version>1.4.1</alibaba.seata.version>
<spring.plugin.version>2.0.0.RELEASE</spring.plugin.version> <spring.plugin.version>2.0.0.RELEASE</spring.plugin.version>
<spring.boot.version>2.2.11.RELEASE</spring.boot.version> <spring.boot.version>2.4.1</spring.boot.version>
<spring.cloud.version>Hoxton.SR8</spring.cloud.version> <spring.cloud.version>2020.0.0</spring.cloud.version>
<spring.platform.version>Cairo-SR8</spring.platform.version> <spring.platform.version>Cairo-SR8</spring.platform.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@ -141,6 +141,11 @@
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.0.Final</version>
</dependency>
<dependency> <dependency>
<groupId>net.dreamlu</groupId> <groupId>net.dreamlu</groupId>
<artifactId>mica-auto</artifactId> <artifactId>mica-auto</artifactId>