🎉 2.5.0.RELEASE

This commit is contained in:
smallchill 2019-09-23 12:51:47 +08:00
parent f7f9875031
commit 6cb32aefd5
65 changed files with 3223 additions and 283 deletions

View File

@ -5,7 +5,7 @@
<parent>
<groupId>org.springblade</groupId>
<artifactId>blade-tool</artifactId>
<version>2.4.1</version>
<version>2.5.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -111,6 +111,12 @@
<artifactId>ehcache</artifactId>
<version>2.10.5</version>
</dependency>
<!-- Druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.18</version>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>

View File

@ -29,12 +29,37 @@ spring:
add-mappings: false
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
connection-test-query: SELECT 1 FROM DUAL
connection-timeout: 30000
maximum-pool-size: 5
max-lifetime: 1800000
minimum-idle: 1
druid:
initial-size: 5
max-active: 20
min-idle: 5
max-wait: 60000
# MySql、PostgreSQL校验
validation-query: select 1
# Oracle校验
#validation-query: select 1 from dual
validation-query-timeout: 2000
test-on-borrow: false
test-on-return: false
test-while-idle: true
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
stat-view-servlet:
enabled: true
login-username: blade
login-password: 1qaz@WSX
web-stat-filter:
enabled: true
url-pattern: /*
exclusions: '*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*'
session-stat-enable: true
session-stat-max-count: 10
#hikari:
#connection-test-query: SELECT 1 FROM DUAL
#connection-timeout: 30000
#maximum-pool-size: 5
#max-lifetime: 1800000
#minimum-idle: 1
devtools:
restart:
log-condition-evaluation-delta: false
@ -75,7 +100,7 @@ mybatis-plus:
swagger:
title: SpringBlade 接口文档系统
description: SpringBlade 接口文档系统
version: 2.4.1
version: 2.5.0
license: Powered By SpringBlade
licenseUrl: https://bladex.vip
terms-of-service-url: https://bladex.vip

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>2.4.1</version>
<version>2.5.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -16,34 +16,27 @@
<dependencies>
<!--Blade-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
<groupId>org.springblade</groupId>
<artifactId>blade-core-secure</artifactId>
<version>${blade.tool.version}</version>
</dependency>
<!--Spring-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>${spring.boot.admin.version}</version>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
<artifactId>spring-cloud-stream</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<!--Feign-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>10.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
@ -55,6 +48,28 @@
</exclusion>
</exclusions>
</dependency>
<!--Hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Admin -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>${spring.boot.admin.version}</version>
</dependency>
<!-- Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>

View File

@ -0,0 +1,38 @@
/**
* 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.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 "";
}

View File

@ -0,0 +1,37 @@
/**
* 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.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 "";
}

View File

@ -0,0 +1,106 @@
/**
* 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.annotation;
import org.springframework.core.annotation.AliasFor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import java.lang.annotation.*;
/**
* 版本号处理
*
* <p>
* 1. url 版本号添加到 url
* 2. Accept 版本application/vnd.blade.VERSION+json
* </p>
*
* @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 "";
}

View File

@ -1,52 +0,0 @@
/**
* 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 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();
}
}

View File

@ -0,0 +1,44 @@
/**
* 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.feign;
import feign.Target;
import feign.hystrix.FallbackFactory;
import lombok.AllArgsConstructor;
import org.springframework.cglib.proxy.Enhancer;
/**
* 默认 Fallback避免写过多fallback类
*
* @param <T> 泛型标记
* @author L.cm
*/
@AllArgsConstructor
public class BladeFallbackFactory<T> implements FallbackFactory<T> {
private final Target<T> target;
@Override
@SuppressWarnings("unchecked")
public T create(Throwable cause) {
final Class<T> targetType = target.type();
final String targetName = target.name();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetType);
enhancer.setUseCache(true);
enhancer.setCallback(new BladeFeignFallback<>(targetType, targetName, cause));
return (T) enhancer.create();
}
}

View File

@ -0,0 +1,96 @@
/*
* 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
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

@ -0,0 +1,91 @@
/**
* 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.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<T> implements MethodInterceptor {
private final Class<T> targetType;
private final String targetName;
private final Throwable cause;
private final String code = "code";
@Nullable
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
String errorMessage = cause.getMessage();
log.error("BladeFeignFallback:[{}.{}] serviceId:[{}] message:[{}]", targetType.getName(), method.getName(), targetName, errorMessage);
Class<?> returnType = method.getReturnType();
// 暂时不支持 fluxrx异步等返回值不是 R直接返回 null
if (R.class != returnType) {
return null;
}
// FeignException
if (!(cause instanceof FeignException)) {
return R.fail(ResultCode.INTERNAL_SERVER_ERROR, errorMessage);
}
FeignException exception = (FeignException) cause;
byte[] content = exception.content();
// 如果返回的数据为空
if (ObjectUtil.isEmpty(content)) {
return R.fail(ResultCode.INTERNAL_SERVER_ERROR, errorMessage);
}
// 转换成 jsonNode 读取因为直接转换可能 对方放回的并 不是 R 的格式
JsonNode resultNode = JsonUtil.readTree(content);
// 判断是否 R 格式 返回体
if (resultNode.has(code)) {
return JsonUtil.getInstance().convertValue(resultNode, R.class);
}
return R.fail(resultNode.toString());
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
BladeFeignFallback<?> that = (BladeFeignFallback<?>) o;
return targetType.equals(that.targetType);
}
@Override
public int hashCode() {
return Objects.hash(targetType);
}
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* 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.
@ -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
* <p>
* 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
* </p>
*
* @author L.cm
*/
@Slf4j
public class BladeFeignRequestHeaderInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attrs != null) {
HttpServletRequest request = attrs.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String value = request.getHeader(name);
if ("blade-auth".equals(name.toLowerCase())) {
requestTemplate.header(name, value);
}
}
}
HttpHeaders headers = BladeHttpHeadersContextHolder.get();
if (headers != null && !headers.isEmpty()) {
headers.forEach((key, values) -> {
values.forEach(value -> requestTemplate.header(key, value));
});
}
}

View File

@ -0,0 +1,101 @@
/**
* 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.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<AnnotatedParameterProcessor> annotatedParameterProcessors, ConversionService conversionService) {
super(annotatedParameterProcessors, conversionService);
}
@Override
protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {
if (RequestMapping.class.isInstance(methodAnnotation) || methodAnnotation.annotationType().isAnnotationPresent(RequestMapping.class)) {
Class<?> targetType = method.getDeclaringClass();
// url 上的版本优先获取方法上的版本
UrlVersion urlVersion = AnnotatedElementUtils.findMergedAnnotation(method, UrlVersion.class);
// 再次尝试类上的版本
if (urlVersion == null || StringUtil.isBlank(urlVersion.value())) {
urlVersion = AnnotatedElementUtils.findMergedAnnotation(targetType, UrlVersion.class);
}
if (urlVersion != null && StringUtil.isNotBlank(urlVersion.value())) {
String versionUrl = "/" + urlVersion.value();
data.template().uri(versionUrl);
}
// 注意在父类之前 添加 url版本在父类之后处理 Media Types 版本
super.processAnnotationOnMethod(data, methodAnnotation, method);
// 处理 Media Types 版本信息
ApiVersion apiVersion = AnnotatedElementUtils.findMergedAnnotation(method, ApiVersion.class);
// 再次尝试类上的版本
if (apiVersion == null || StringUtil.isBlank(apiVersion.value())) {
apiVersion = AnnotatedElementUtils.findMergedAnnotation(targetType, ApiVersion.class);
}
if (apiVersion != null && StringUtil.isNotBlank(apiVersion.value())) {
BladeMediaType BladeMediaType = new BladeMediaType(apiVersion.value());
data.template().header(HttpHeaders.ACCEPT, BladeMediaType.toString());
}
}
}
/**
* 参考https://gist.github.com/rmfish/0ed59a9af6c05157be2a60c9acea2a10
* @param annotations 注解
* @param paramIndex 参数索引
* @return 是否 http 注解
*/
@Override
protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {
boolean httpAnnotation = super.processAnnotationsOnParameter(data, annotations, paramIndex);
// springMvc 中如果是 Get 请求且参数中是对象 没有声明为@RequestBody 则默认为 Param
if (!httpAnnotation && StringPool.GET.equals(data.template().method().toUpperCase())) {
for (Annotation parameterAnnotation : annotations) {
if (!(parameterAnnotation instanceof RequestBody)) {
return false;
}
}
data.queryMapIndex(paramIndex);
return true;
}
return httpAnnotation;
}
}

View File

@ -0,0 +1,80 @@
/**
* 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.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.
* <p>
* {@link #value()} is an alias for (and mutually exclusive with) this attribute.
* <p>
* 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.
* <p>
* 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 <code>@Configuration</code> for all feign clients. Can contain override
* <code>@Bean</code> 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 {};
}

View File

@ -1,133 +0,0 @@
/**
* 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.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 <T> Callable<T> wrapCallable(Callable<T> callable) {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
return new WrappedCallable<>(callable, requestAttributes);
}
@Override
public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
HystrixProperty<Integer> corePoolSize, HystrixProperty<Integer> maximumPoolSize,
HystrixProperty<Integer> keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
return this.delegate.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime,
unit, workQueue);
}
@Override
public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
HystrixThreadPoolProperties threadPoolProperties) {
return this.delegate.getThreadPool(threadPoolKey, threadPoolProperties);
}
@Override
public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
return this.delegate.getBlockingQueue(maxQueueSize);
}
@Override
public <T> HystrixRequestVariable<T> getRequestVariable(HystrixRequestVariableLifecycle<T> rv) {
return this.delegate.getRequestVariable(rv);
}
static class WrappedCallable<T> implements Callable<T> {
private final Callable<T> target;
private final RequestAttributes requestAttributes;
public WrappedCallable(Callable<T> target, RequestAttributes requestAttributes) {
this.target = target;
this.requestAttributes = requestAttributes;
}
@Override
public T call() throws Exception {
try {
RequestContextHolder.setRequestAttributes(requestAttributes);
return target.call();
} finally {
RequestContextHolder.resetRequestAttributes();
}
}
}
}

View File

@ -0,0 +1,330 @@
/**
* 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.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}. <p> 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.
*
* <p>Example:
* <pre>{@code
* --> POST /greeting http/1.1 (3-byte body)
*
* <-- 200 OK (22ms, 6-byte body)
* }</pre>
*/
BASIC,
/**
* Logs request and response lines and their respective headers.
*
* <p>Example:
* <pre>{@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
* }</pre>
*/
HEADERS,
/**
* Logs request and response lines and their respective headers and bodies (if present).
*
* <p>Example:
* <pre>{@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
* }</pre>
*/
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);
}
}

View File

@ -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<HttpMessageConverter<?>> messageConverters) {
super(messageConverters);
}
}

View File

@ -0,0 +1,31 @@
/**
* 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.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);
}
}

View File

@ -0,0 +1,177 @@
/**
* 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.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<HttpMessageConverter<?>> converters) {
converters.removeIf(x -> x instanceof StringHttpMessageConverter || x instanceof MappingJackson2HttpMessageConverter);
converters.add(new StringHttpMessageConverter(Charsets.UTF_8));
converters.add(new MappingJackson2HttpMessageConverter(objectMapper));
}
}

View File

@ -0,0 +1,59 @@
/**
* 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.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);
}
}

View File

@ -0,0 +1,43 @@
/**
* 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.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);
}
}

View File

@ -0,0 +1,56 @@
/**
* 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 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 <V> 泛型标记
* @author L.cm
*/
public class BladeHttpHeadersCallable<V> implements Callable<V> {
private final Callable<V> delegate;
@Nullable
private HttpHeaders httpHeaders;
public BladeHttpHeadersCallable(Callable<V> delegate,
@Nullable BladeHystrixAccountGetter accountGetter,
BladeHystrixHeadersProperties properties) {
this.delegate = delegate;
this.httpHeaders = BladeHttpHeadersContextHolder.toHeaders(accountGetter, properties);
}
@Override
public V call() throws Exception {
if (httpHeaders == null) {
return delegate.call();
}
try {
BladeHttpHeadersContextHolder.set(httpHeaders);
return delegate.call();
} finally {
BladeHttpHeadersContextHolder.remove();
httpHeaders.clear();
httpHeaders = null;
}
}
}

View File

@ -0,0 +1,100 @@
/**
* 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 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<HttpHeaders> HTTP_HEADERS_HOLDER = new NamedThreadLocal<>("Blade hystrix HttpHeaders");
/**
* 请求和转发的ip
*/
private static final String[] ALLOW_HEADS = new String[]{
"X-Real-IP", "x-forwarded-for", "authorization", "blade-auth", "Authorization", "Blade-Auth"
};
static void set(HttpHeaders httpHeaders) {
HTTP_HEADERS_HOLDER.set(httpHeaders);
}
@Nullable
public static HttpHeaders get() {
return HTTP_HEADERS_HOLDER.get();
}
static void remove() {
HTTP_HEADERS_HOLDER.remove();
}
@Nullable
public static HttpHeaders toHeaders(
@Nullable BladeHystrixAccountGetter accountGetter,
BladeHystrixHeadersProperties properties) {
HttpServletRequest request = WebUtil.getRequest();
if (request == null) {
return null;
}
HttpHeaders headers = new HttpHeaders();
String accountHeaderName = properties.getAccount();
// 如果配置有 account 读取器
if (accountGetter != null) {
String xAccountHeader = accountGetter.get(request);
if (StringUtil.isNotBlank(xAccountHeader)) {
headers.add(accountHeaderName, xAccountHeader);
}
}
List<String> allowHeadsList = new ArrayList<>(Arrays.asList(ALLOW_HEADS));
// 如果有传递 account header 继续往下层传递
allowHeadsList.add(accountHeaderName);
// 传递请求头
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
List<String> allowed = properties.getAllowed();
String pattern = properties.getPattern();
while (headerNames.hasMoreElements()) {
String key = headerNames.nextElement();
// 只支持配置的 header
if (allowHeadsList.contains(key) || allowed.contains(key) || PatternMatchUtils.simpleMatch(pattern, key)) {
String values = request.getHeader(key);
// header value 不为空的 传递
if (StringUtil.isNotBlank(values)) {
headers.add(key, values);
}
}
}
}
return headers.isEmpty() ? null : headers;
}
}

View File

@ -0,0 +1,38 @@
/**
* 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 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);
}

View File

@ -0,0 +1,71 @@
/**
* 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

@ -0,0 +1,93 @@
/**
* 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

@ -0,0 +1,54 @@
/**
* 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.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<String> allowed = Arrays.asList("X-Real-IP", "x-forwarded-for", "authorization", "blade-auth", "Authorization", "Blade-Auth");
}

View File

@ -0,0 +1,32 @@
package org.springblade.core.cloud.stream;
import org.springframework.cloud.stream.annotation.Input;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;
/**
* 服务异常 Streams
*
* @author L.cm
*/
public interface ServiceErrorStreams {
String INPUT = "service-error-in";
String OUTPUT = "service-error-out";
/**
* input
*
* @return SubscribableChannel
*/
@Input(INPUT)
SubscribableChannel subscribablebChannel();
/**
* output
*
* @return MessageChannel
*/
@Output(OUTPUT)
MessageChannel messageChannel();
}

View File

@ -0,0 +1,48 @@
/**
* 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.version;
import lombok.Getter;
import org.springframework.http.MediaType;
/**
* blade Media Typesapplication/vnd.github.VERSION+json
*
* <p>
* https://developer.github.com/v3/media/
* </p>
*
* @author L.cm
*/
@Getter
public class BladeMediaType {
private static final String MEDIA_TYPE_TEMP = "application/vnd.%s.%s+json";
private final String appName = "blade";
private final String version;
private final MediaType mediaType;
public BladeMediaType(String version) {
this.version = version;
this.mediaType = MediaType.valueOf(String.format(MEDIA_TYPE_TEMP, appName, version));
}
@Override
public String toString() {
return mediaType.toString();
}
}

View File

@ -0,0 +1,103 @@
/**
* 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.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 版本处理
*
* <p>
* url: /v1/user/{id}
* header: Accept application/vnd.blade.VERSION+json
* </p>
*
* 注意c 代表客户端版本
*
* @author L.cm
*/
public class BladeRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
@Nullable
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo mappinginfo = super.getMappingForMethod(method, handlerType);
if (mappinginfo != null) {
RequestMappingInfo apiVersionMappingInfo = getApiVersionMappingInfo(method, handlerType);
return apiVersionMappingInfo == null ? mappinginfo : apiVersionMappingInfo.combine(mappinginfo);
}
return null;
}
@Nullable
private RequestMappingInfo getApiVersionMappingInfo(Method method, Class<?> handlerType) {
// url 上的版本优先获取方法上的版本
UrlVersion urlVersion = AnnotatedElementUtils.findMergedAnnotation(method, UrlVersion.class);
// 再次尝试类上的版本
if (urlVersion == null || StringUtil.isBlank(urlVersion.value())) {
urlVersion = AnnotatedElementUtils.findMergedAnnotation(handlerType, UrlVersion.class);
}
// Media Types 版本信息
ApiVersion apiVersion = AnnotatedElementUtils.findMergedAnnotation(method, ApiVersion.class);
// 再次尝试类上的版本
if (apiVersion == null || StringUtil.isBlank(apiVersion.value())) {
apiVersion = AnnotatedElementUtils.findMergedAnnotation(handlerType, ApiVersion.class);
}
boolean nonUrlVersion = urlVersion == null || StringUtil.isBlank(urlVersion.value());
boolean nonApiVersion = apiVersion == null || StringUtil.isBlank(apiVersion.value());
// 先判断同时不纯在
if (nonUrlVersion && nonApiVersion) {
return null;
}
// 如果 header 版本不存在
RequestMappingInfo.Builder mappingInfoBuilder = null;
if (nonApiVersion) {
mappingInfoBuilder = RequestMappingInfo.paths(urlVersion.value());
} else {
mappingInfoBuilder = RequestMappingInfo.paths(StringPool.EMPTY);
}
// 如果url版本不存在
if (nonUrlVersion) {
String vsersionMediaTypes = new BladeMediaType(apiVersion.value()).toString();
mappingInfoBuilder.produces(vsersionMediaTypes);
}
return mappingInfoBuilder.build();
}
@Override
protected void handlerMethodsInitialized(Map<RequestMappingInfo, HandlerMethod> handlerMethods) {
// 打印路由信息 spring boot 2.1 去掉了这个 日志的打印
if (logger.isInfoEnabled()) {
for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethods.entrySet()) {
RequestMappingInfo mapping = entry.getKey();
HandlerMethod handlerMethod = entry.getValue();
logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
}
}
super.handlerMethodsInitialized(handlerMethods);
}
}

View File

@ -0,0 +1,43 @@
/**
* 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.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;
}
}

View File

@ -0,0 +1,37 @@
/**
* 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.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();
}
}

View File

@ -0,0 +1,230 @@
/**
* 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

@ -0,0 +1,99 @@
/*
* 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

@ -0,0 +1,39 @@
/*
* 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>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>2.4.1</version>
<version>2.5.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

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

View File

@ -100,6 +100,7 @@ public class BladeApplication {
props.setProperty("spring.cloud.nacos.config.prefix", NacosConstant.NACOS_CONFIG_PREFIX);
props.setProperty("spring.cloud.nacos.config.file-extension", NacosConstant.NACOS_CONFIG_FORMAT);
props.setProperty("spring.cloud.sentinel.transport.dashboard", SentinelConstant.SENTINEL_ADDR);
props.setProperty("spring.cloud.alibaba.seata.tx-service-group", appName.concat(NacosConstant.NACOS_GROUP_SUFFIX));
// 加载自定义组件
List<LauncherService> launcherList = new ArrayList<>();
ServiceLoader.load(LauncherService.class).forEach(launcherList::add);

View File

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

View File

@ -32,6 +32,11 @@ public interface NacosConstant {
*/
String NACOS_CONFIG_PREFIX = "blade";
/**
* nacos 组配置后缀
*/
String NACOS_GROUP_SUFFIX = "-group";
/**
* nacos 配置文件类型
*/

View File

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

View File

@ -24,7 +24,7 @@ import org.springblade.core.tool.utils.DateUtil;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Date;
/**
* logApilogErrorlogUsual的父类拥有相同的属性值
@ -101,6 +101,6 @@ public class LogAbstract implements Serializable {
*/
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
protected LocalDateTime createTime;
protected Date createTime;
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2029, DreamLu 卢春梦 (596392912@qq.com & www.dreamlu.net).
* 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.
@ -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) {

View File

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

View File

@ -24,7 +24,7 @@ import org.springblade.core.tool.utils.DateUtil;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Date;
/**
* 基础实体类
@ -46,7 +46,7 @@ public class BaseEntity implements Serializable {
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
@ApiModelProperty(value = "创建时间")
private LocalDateTime createTime;
private Date createTime;
/**
* 更新人
@ -60,7 +60,7 @@ public class BaseEntity implements Serializable {
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
@ApiModelProperty(value = "更新时间")
private LocalDateTime updateTime;
private Date updateTime;
/**
* 状态[1:正常]

View File

@ -20,12 +20,13 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springblade.core.secure.BladeUser;
import org.springblade.core.secure.utils.SecureUtil;
import org.springblade.core.tool.constant.BladeConstant;
import org.springblade.core.tool.utils.DateUtil;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotEmpty;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
/**
@ -53,7 +54,7 @@ public class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseEntity> exte
entity.setCreateUser(user.getUserId());
entity.setUpdateUser(user.getUserId());
}
LocalDateTime now = LocalDateTime.now();
Date now = DateUtil.now();
entity.setCreateTime(now);
entity.setUpdateTime(now);
if (entity.getStatus() == null) {
@ -69,7 +70,7 @@ public class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseEntity> exte
if (user != null) {
entity.setUpdateUser(user.getUserId());
}
entity.setUpdateTime(LocalDateTime.now());
entity.setUpdateTime(DateUtil.now());
return super.updateById(entity);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,50 @@
package org.springblade.core.tool.convert;
import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.lang.Nullable;
import org.springframework.util.StringValueResolver;
/**
* 类型 转换 服务添加了 IEnum 转换
*
* @author L.cm
*/
public class BladeConversionService extends ApplicationConversionService {
@Nullable
private static volatile BladeConversionService SHARED_INSTANCE;
public BladeConversionService() {
this(null);
}
public BladeConversionService(@Nullable StringValueResolver embeddedValueResolver) {
super(embeddedValueResolver);
super.addConverter(new EnumToStringConverter());
super.addConverter(new StringToEnumConverter());
}
/**
* Return a shared default application {@code ConversionService} instance, lazily
* building it once needed.
* <p>
* 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;
}
}

View File

@ -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<String, TypeDescriptor> TYPE_CACHE = new ConcurrentHashMap<>();
private final Class<?> targetClazz;
/**
* cglib convert
* @param value 源对象属性
* @param target 目标对象属性类
* @param fieldName 目标的field名原为 set 方法名BladeBeanCopier 里做了更改
* @return {Object}
*/
@Override
@Nullable
public Object convert(Object value, Class target, final Object fieldName) {
if (value == null) {
return null;
}
// 类型一样不需要转换
if (ClassUtil.isAssignableValue(target, value)) {
return value;
}
try {
TypeDescriptor targetDescriptor = BladeConverter.getTypeDescriptor(targetClazz, (String) fieldName);
return ConvertUtil.convert(value, targetDescriptor);
} catch (Throwable e) {
log.warn("BladeConverter error", e);
return null;
}
}
private static TypeDescriptor getTypeDescriptor(final Class<?> clazz, final String fieldName) {
String srcCacheKey = clazz.getName() + fieldName;
return TYPE_CACHE.computeIfAbsent(srcCacheKey, Try.of(k -> {
// 这里 property 理论上不会为 null
Field field = ReflectUtil.getField(clazz, fieldName);
if (field == null) {
throw new NoSuchFieldException(fieldName);
}
return new TypeDescriptor(field);
}));
}
}

View File

@ -0,0 +1,109 @@
package org.springblade.core.tool.convert;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.tool.utils.ConvertUtil;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.lang.Nullable;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* 接收参数 jackson Enum - String 转换
*
* @author L.cm
*/
@Slf4j
public class EnumToStringConverter implements ConditionalGenericConverter {
/**
* 缓存 Enum 类信息提供性能
*/
private static final ConcurrentMap<Class<?>, AccessibleObject> ENUM_CACHE_MAP = new ConcurrentHashMap<>(8);
@Nullable
private static AccessibleObject getAnnotation(Class<?> clazz) {
Set<AccessibleObject> accessibleObjects = new HashSet<>();
// JsonValue METHOD, FIELD
Field[] fields = clazz.getDeclaredFields();
Collections.addAll(accessibleObjects, fields);
// methods
Method[] methods = clazz.getDeclaredMethods();
Collections.addAll(accessibleObjects, methods);
for (AccessibleObject accessibleObject : accessibleObjects) {
// 复用 jackson JsonValue 注解
JsonValue jsonValue = accessibleObject.getAnnotation(JsonValue.class);
if (jsonValue != null && jsonValue.value()) {
accessibleObject.setAccessible(true);
return accessibleObject;
}
}
return null;
}
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return true;
}
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
Set<ConvertiblePair> pairSet = new HashSet<>(3);
pairSet.add(new ConvertiblePair(Enum.class, String.class));
pairSet.add(new ConvertiblePair(Enum.class, Integer.class));
pairSet.add(new ConvertiblePair(Enum.class, Long.class));
return Collections.unmodifiableSet(pairSet);
}
@Override
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
}
Class<?> sourceClazz = sourceType.getType();
AccessibleObject accessibleObject = ENUM_CACHE_MAP.computeIfAbsent(sourceClazz, EnumToStringConverter::getAnnotation);
Class<?> targetClazz = targetType.getType();
// 如果为null走默认的转换
if (accessibleObject == null) {
if (String.class == targetClazz) {
return ((Enum) source).name();
}
int ordinal = ((Enum) source).ordinal();
return ConvertUtil.convert(ordinal, targetClazz);
}
try {
return EnumToStringConverter.invoke(sourceClazz, accessibleObject, source, targetClazz);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return null;
}
@Nullable
private static Object invoke(Class<?> clazz, AccessibleObject accessibleObject, Object source, Class<?> targetClazz)
throws IllegalAccessException, InvocationTargetException {
Object value = null;
if (accessibleObject instanceof Field) {
Field field = (Field) accessibleObject;
value = field.get(source);
} else if (accessibleObject instanceof Method) {
Method method = (Method) accessibleObject;
Class<?> paramType = method.getParameterTypes()[0];
// 类型转换
Object object = ConvertUtil.convert(source, paramType);
value = method.invoke(clazz, object);
}
if (value == null) {
return null;
}
return ConvertUtil.convert(value, targetClazz);
}
}

View File

@ -0,0 +1,109 @@
package org.springblade.core.tool.convert;
import com.fasterxml.jackson.annotation.JsonCreator;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.tool.utils.ConvertUtil;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.lang.Nullable;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* 接收参数 jackson String - Enum 转换
*
* @author L.cm
*/
@Slf4j
public class StringToEnumConverter implements ConditionalGenericConverter {
/**
* 缓存 Enum 类信息提供性能
*/
private static final ConcurrentMap<Class<?>, AccessibleObject> ENUM_CACHE_MAP = new ConcurrentHashMap<>(8);
@Nullable
private static AccessibleObject getAnnotation(Class<?> clazz) {
Set<AccessibleObject> accessibleObjects = new HashSet<>();
// JsonCreator METHOD, CONSTRUCTOR
Constructor<?>[] constructors = clazz.getConstructors();
Collections.addAll(accessibleObjects, constructors);
// methods
Method[] methods = clazz.getDeclaredMethods();
Collections.addAll(accessibleObjects, methods);
for (AccessibleObject accessibleObject : accessibleObjects) {
// 复用 jackson JsonCreator注解
JsonCreator jsonCreator = accessibleObject.getAnnotation(JsonCreator.class);
if (jsonCreator != null && JsonCreator.Mode.DISABLED != jsonCreator.mode()) {
accessibleObject.setAccessible(true);
return accessibleObject;
}
}
return null;
}
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return true;
}
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(String.class, Enum.class));
}
@Nullable
@Override
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (StringUtil.isBlank((String) source)) {
return null;
}
Class<?> clazz = targetType.getType();
AccessibleObject accessibleObject = ENUM_CACHE_MAP.computeIfAbsent(clazz, StringToEnumConverter::getAnnotation);
String value = ((String) source).trim();
// 如果为null走默认的转换
if (accessibleObject == null) {
return valueOf(clazz, value);
}
try {
return StringToEnumConverter.invoke(clazz, accessibleObject, value);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return null;
}
@SuppressWarnings("unchecked")
private static <T extends Enum<T>> T valueOf(Class<?> clazz, String value){
return Enum.valueOf((Class<T>) clazz, value);
}
@Nullable
private static Object invoke(Class<?> clazz, AccessibleObject accessibleObject, String value)
throws IllegalAccessException, InvocationTargetException, InstantiationException {
if (accessibleObject instanceof Constructor) {
Constructor constructor = (Constructor) accessibleObject;
Class<?> paramType = constructor.getParameterTypes()[0];
// 类型转换
Object object = ConvertUtil.convert(value, paramType);
return constructor.newInstance(object);
}
if (accessibleObject instanceof Method) {
Method method = (Method) accessibleObject;
Class<?> paramType = method.getParameterTypes()[0];
// 类型转换
Object object = ConvertUtil.convert(value, paramType);
return method.invoke(clazz, object);
}
return null;
}
}

View File

@ -0,0 +1,83 @@
package org.springblade.core.tool.utils;
import lombok.experimental.UtilityClass;
import org.springblade.core.tool.convert.BladeConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.lang.Nullable;
/**
* 基于 spring ConversionService 类型转换
*
* @author L.cm
*/
@UtilityClass
@SuppressWarnings("unchecked")
public class ConvertUtil {
/**
* Convenience operation for converting a source object to the specified targetType.
* {@link TypeDescriptor#forObject(Object)}.
* @param source the source object
* @param targetType the target type
* @param <T> 泛型标记
* @return the converted value
* @throws IllegalArgumentException if targetType is {@code null},
* or sourceType is {@code null} but source is not {@code null}
*/
@Nullable
public static <T> T convert(@Nullable Object source, Class<T> targetType) {
if (source == null) {
return null;
}
if (ClassUtil.isAssignableValue(targetType, source)) {
return (T) source;
}
GenericConversionService conversionService = BladeConversionService.getInstance();
return conversionService.convert(source, targetType);
}
/**
* Convenience operation for converting a source object to the specified targetType,
* where the target type is a descriptor that provides additional conversion context.
* {@link TypeDescriptor#forObject(Object)}.
* @param source the source object
* @param sourceType the source type
* @param targetType the target type
* @param <T> 泛型标记
* @return the converted value
* @throws IllegalArgumentException if targetType is {@code null},
* or sourceType is {@code null} but source is not {@code null}
*/
@Nullable
public static <T> T convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
}
GenericConversionService conversionService = BladeConversionService.getInstance();
return (T) conversionService.convert(source, sourceType, targetType);
}
/**
* Convenience operation for converting a source object to the specified targetType,
* where the target type is a descriptor that provides additional conversion context.
* Simply delegates to {@link #convert(Object, TypeDescriptor, TypeDescriptor)} and
* encapsulates the construction of the source type descriptor using
* {@link TypeDescriptor#forObject(Object)}.
* @param source the source object
* @param targetType the target type
* @param <T> 泛型标记
* @return the converted value
* @throws IllegalArgumentException if targetType is {@code null},
* or sourceType is {@code null} but source is not {@code null}
*/
@Nullable
public static <T> T convert(@Nullable Object source, TypeDescriptor targetType) {
if (source == null) {
return null;
}
GenericConversionService conversionService = BladeConversionService.getInstance();
return (T) conversionService.convert(source, targetType);
}
}

View File

@ -39,6 +39,15 @@ public class DateUtil {
public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(DateUtil.PATTERN_DATE);
public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern(DateUtil.PATTERN_TIME);
/**
* 获取当前日期
*
* @return 当前日期
*/
public static Date now() {
return new Date();
}
/**
* 添加年
*

View File

@ -1,18 +1,17 @@
/*
* Copyright (c) 2018-2028, DreamLu All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the dreamlu.net developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: DreamLu 卢春梦 (596392912@qq.com)
/**
* 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.tool.utils;

View File

@ -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<PropertyDescriptor> properties = new ArrayList<>(all.length);
for (PropertyDescriptor pd : all) {
if (read && pd.getReadMethod() != null) {
properties.add(pd);
} else if (write && pd.getWriteMethod() != null) {
properties.add(pd);
}
}
return properties.toArray(new PropertyDescriptor[0]);
}
} catch (BeansException ex) {
throw new CodeGenerationException(ex);
}
}
/**
* 获取 bean 的属性信息
* @param propertyType 类型
* @param propertyName 属性名
* @return {Property}
*/
@Nullable
public static Property getProperty(Class<?> propertyType, String propertyName) {
PropertyDescriptor propertyDescriptor = BeanUtil.getPropertyDescriptor(propertyType, propertyName);
if (propertyDescriptor == null) {
return null;
}
return ReflectUtil.getProperty(propertyType, propertyDescriptor, propertyName);
}
/**
* 获取 bean 的属性信息
* @param propertyType 类型
* @param propertyDescriptor PropertyDescriptor
* @param propertyName 属性名
* @return {Property}
*/
public static Property getProperty(Class<?> propertyType, PropertyDescriptor propertyDescriptor, String propertyName) {
Method readMethod = propertyDescriptor.getReadMethod();
Method writeMethod = propertyDescriptor.getWriteMethod();
return new Property(propertyType, readMethod, writeMethod, propertyName);
}
/**
* 获取 bean 的属性信息
* @param propertyType 类型
* @param propertyName 属性名
* @return {Property}
*/
@Nullable
public static TypeDescriptor getTypeDescriptor(Class<?> propertyType, String propertyName) {
Property property = ReflectUtil.getProperty(propertyType, propertyName);
if (property == null) {
return null;
}
return new TypeDescriptor(property);
}
/**
* 获取 类属性信息
* @param propertyType 类型
* @param propertyDescriptor PropertyDescriptor
* @param propertyName 属性名
* @return {Property}
*/
public static TypeDescriptor getTypeDescriptor(Class<?> propertyType, PropertyDescriptor propertyDescriptor, String propertyName) {
Method readMethod = propertyDescriptor.getReadMethod();
Method writeMethod = propertyDescriptor.getWriteMethod();
Property property = new Property(propertyType, readMethod, writeMethod, propertyName);
return new TypeDescriptor(property);
}
/**
* 获取 类属性
* @param clazz 类信息
* @param fieldName 属性名
* @return Field
*/
@Nullable
public static Field getField(Class<?> clazz, String fieldName) {
while (clazz != Object.class) {
try {
return clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
clazz = clazz.getSuperclass();
}
}
return null;
}
/**
* 获取 所有 field 属性上的注解
* @param clazz
* @param fieldName 属性名
* @param annotationClass 注解
* @param <T> 注解泛型
* @return 注解
*/
@Nullable
public static <T extends Annotation> T getAnnotation(Class<?> clazz, String fieldName, Class<T> annotationClass) {
Field field = ReflectUtil.getField(clazz, fieldName);
if (field == null) {
return null;
}
return field.getAnnotation(annotationClass);
}
}

View File

@ -82,5 +82,8 @@ public interface StringPool {
char L_A = 'a';
char U_Z = 'Z';
char L_Z = 'z';
String UNKNOWN = "unknown";
String GET = "GET";
String POST = "POST";
}

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>2.5.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>blade-core-transaction</artifactId>
<name>${project.artifactId}</name>
<version>${blade.tool.version}</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-mybatis</artifactId>
<version>${blade.tool.version}</version>
</dependency>
<!-- Cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</dependency>
<!-- Seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata</artifactId>
<version>${alibaba.cloud.version}</version>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>${alibaba.seata.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,39 @@
/**
* Copyright (c) 2018-2028, lengleng (wangiegie@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.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 {
}

View File

@ -0,0 +1,79 @@
/**
* Copyright (c) 2018-2028, lengleng (wangiegie@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.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);
}
}

View File

@ -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 = ""
}
}

14
pom.xml
View File

@ -5,7 +5,7 @@
<groupId>org.springblade</groupId>
<artifactId>blade-tool</artifactId>
<version>2.4.1</version>
<version>2.5.0</version>
<packaging>pom</packaging>
<name>blade-tool</name>
<description>
@ -36,13 +36,13 @@
</scm>
<properties>
<blade.tool.version>2.4.1</blade.tool.version>
<blade.tool.version>2.5.0</blade.tool.version>
<java.version>1.8</java.version>
<maven.plugin.version>3.8.0</maven.plugin.version>
<swagger.version>2.9.2</swagger.version>
<swagger.models.version>1.5.21</swagger.models.version>
<swagger.bootstrapui.version>1.9.4</swagger.bootstrapui.version>
<swagger.bootstrapui.version>1.9.6</swagger.bootstrapui.version>
<mybatis.plus.version>3.1.2</mybatis.plus.version>
<curator.framework.version>4.0.1</curator.framework.version>
<protostuff.version>1.6.0</protostuff.version>
@ -50,11 +50,12 @@
<spring.boot.admin.version>2.1.5</spring.boot.admin.version>
<mica.auto.version>1.1.0</mica.auto.version>
<alibaba.cloud.version>2.1.0.RELEASE</alibaba.cloud.version>
<alibaba.seata.version>0.8.1</alibaba.seata.version>
<spring.boot.version>2.1.7.RELEASE</spring.boot.version>
<spring.cloud.version>Greenwich.SR2</spring.cloud.version>
<spring.boot.version>2.1.8.RELEASE</spring.boot.version>
<spring.cloud.version>Greenwich.SR3</spring.cloud.version>
<spring.platform.version>Cairo-SR8</spring.platform.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
@ -71,6 +72,7 @@
<module>blade-core-test</module>
<module>blade-core-tool</module>
<module>blade-core-oss</module>
<module>blade-core-transaction</module>
</modules>
<dependencyManagement>