This commit is contained in:
2025-11-28 16:23:32 +08:00
commit a9e0e16c29
826 changed files with 89805 additions and 0 deletions

54
blade-core-cloud/pom.xml Normal file
View File

@@ -0,0 +1,54 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>BladeX-Tool</artifactId>
<groupId>org.springblade</groupId>
<version>${revision}</version>
</parent>
<artifactId>blade-core-cloud</artifactId>
<name>${project.artifactId}</name>
<version>${project.parent.version}</version>
<packaging>jar</packaging>
<dependencies>
<!-- Blade -->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-launch</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-context</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-auth</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-loadbalancer</artifactId>
</dependency>
<!-- Admin -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
</dependency>
<!-- Sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- Auto -->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-auto</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,48 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
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,47 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
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,116 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
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

@@ -0,0 +1,49 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.cloud.client;
import org.springblade.core.launch.constant.AppConstant;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import java.lang.annotation.*;
/**
* Cloud启动注解配置
*
* @author Chill
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@EnableDiscoveryClient
@EnableFeignClients(AppConstant.BASE_PACKAGES)
@SpringBootApplication
public @interface BladeCloudApplication {
}

View File

@@ -0,0 +1,54 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.feign;
import feign.Target;
import org.springframework.cloud.openfeign.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,111 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
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.*;
/**
* 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 static 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();
// 集合类型反馈空集合
if (List.class == returnType || Collection.class == returnType) {
return Collections.emptyList();
}
if (Set.class == returnType) {
return Collections.emptySet();
}
if (Map.class == returnType) {
return Collections.emptyMap();
}
// 暂时不支持 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

@@ -0,0 +1,57 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.feign;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springblade.core.tool.constant.BladeConstant;
import org.springblade.core.tool.utils.ThreadLocalUtil;
import org.springframework.http.HttpHeaders;
/**
* feign 传递Request header
*
* <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
*/
public class BladeFeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
HttpHeaders headers = ThreadLocalUtil.get(BladeConstant.CONTEXT_KEY);
if (headers != null && !headers.isEmpty()) {
headers.forEach((key, values) ->
values.forEach(value -> requestTemplate.header(key, value))
);
}
}
}

View File

@@ -0,0 +1,90 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
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

@@ -0,0 +1,39 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.http;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
/**
* http 配置
*
* @author L.cm
*/
@AutoConfiguration
@EnableConfigurationProperties(BladeHttpProperties.class)
public class BladeHttpConfiguration {
}

View File

@@ -0,0 +1,74 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.http;
import lombok.Getter;
import lombok.Setter;
import org.springblade.core.launch.log.BladeLogLevel;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import java.util.concurrent.TimeUnit;
/**
* http 配置
*
* @author L.cm
*/
@Getter
@Setter
@RefreshScope
@ConfigurationProperties("blade.http")
public class BladeHttpProperties {
/**
* 最大连接数默认200
*/
private int maxConnections = 200;
/**
* 连接存活时间默认900L
*/
private long timeToLive = 900L;
/**
* 连接池存活时间单位,默认:秒
*/
private TimeUnit timeUnit = TimeUnit.SECONDS;
/**
* 链接超时默认2000毫秒
*/
private int connectionTimeout = 2000;
/**
* 是否支持重定向默认true
*/
private boolean followRedirects = true;
/**
* 关闭证书校验
*/
private boolean disableSslValidation = true;
/**
* 日志级别
*/
private BladeLogLevel level = BladeLogLevel.NONE;
}

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,200 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.http;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import org.springblade.core.cloud.http.client.OkHttp3ClientHttpRequestFactory;
import org.springblade.core.cloud.http.logger.HttpLoggingInterceptor;
import org.springblade.core.cloud.http.logger.OkHttpSlf4jLogger;
import org.springblade.core.tool.ssl.DisableValidationTrustManager;
import org.springblade.core.tool.ssl.TrustAllHostNames;
import org.springblade.core.tool.utils.Charsets;
import org.springblade.core.tool.utils.Holder;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Http RestTemplateHeaderInterceptor 配置
*
* @author L.cm
*/
@Slf4j
@RequiredArgsConstructor
@AutoConfiguration
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty(value = "blade.http.enabled", matchIfMissing = true)
public class RestTemplateConfiguration {
private final BladeHttpProperties properties;
/**
* okhttp3 请求日志拦截器
*
* @return HttpLoggingInterceptor
*/
@Bean
public HttpLoggingInterceptor loggingInterceptor() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(new OkHttpSlf4jLogger());
interceptor.setLevel(properties.getLevel());
return interceptor;
}
/**
* okhttp3 链接池配置
*
* @return okhttp3.ConnectionPool
*/
@Bean
@ConditionalOnMissingBean
public ConnectionPool httpClientConnectionPool() {
int maxTotalConnections = properties.getMaxConnections();
long timeToLive = properties.getTimeToLive();
TimeUnit ttlUnit = properties.getTimeUnit();
return new ConnectionPool(maxTotalConnections, timeToLive, ttlUnit);
}
/**
* 配置OkHttpClient
*
* @param connectionPool 链接池配置
* @param interceptor 拦截器
* @return OkHttpClient
*/
@Bean
@ConditionalOnMissingBean
public OkHttpClient okHttpClient(ConnectionPool connectionPool, HttpLoggingInterceptor interceptor) {
boolean followRedirects = properties.isFollowRedirects();
int connectTimeout = properties.getConnectionTimeout();
return this.createBuilder(properties.isDisableSslValidation())
.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.followRedirects(followRedirects)
.connectionPool(connectionPool)
.addInterceptor(interceptor)
.build();
}
private OkHttpClient.Builder createBuilder(boolean disableSslValidation) {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
if (disableSslValidation) {
try {
X509TrustManager disabledTrustManager = DisableValidationTrustManager.INSTANCE;
TrustManager[] trustManagers = new TrustManager[]{disabledTrustManager};
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustManagers, Holder.SECURE_RANDOM);
SSLSocketFactory disabledSslSocketFactory = sslContext.getSocketFactory();
builder.sslSocketFactory(disabledSslSocketFactory, disabledTrustManager);
builder.hostnameVerifier(TrustAllHostNames.INSTANCE);
} catch (NoSuchAlgorithmException | KeyManagementException e) {
log.warn("Error setting SSLSocketFactory in OKHttpClient", e);
}
}
return builder;
}
@Bean
public RestTemplateHeaderInterceptor requestHeaderInterceptor() {
return new RestTemplateHeaderInterceptor();
}
@AutoConfiguration
@RequiredArgsConstructor
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty(value = "blade.http.rest-template.enable")
public static class RestTemplateAutoConfiguration {
private final ApplicationContext context;
/**
* 普通的 RestTemplate不透传请求头一般只做外部 http 调用
*
* @param okHttpClient OkHttpClient
* @return RestTemplate
*/
@Bean
@ConditionalOnMissingBean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder, OkHttpClient okHttpClient) {
restTemplateBuilder.requestFactory(() -> new OkHttp3ClientHttpRequestFactory(okHttpClient));
RestTemplate restTemplate = restTemplateBuilder.build();
configMessageConverters(context, restTemplate.getMessageConverters());
return restTemplate;
}
}
@AutoConfiguration
@RequiredArgsConstructor
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty(value = "blade.http.lb-rest-template.enable")
public static class LbRestTemplateAutoConfiguration {
private final ApplicationContext context;
/**
* 支持负载均衡的 LbRestTemplate
*
* @param okHttpClient OkHttpClient
* @return LbRestTemplate
*/
@Bean
@LoadBalanced
@ConditionalOnMissingBean
public LbRestTemplate lbRestTemplate(RestTemplateBuilder restTemplateBuilder, OkHttpClient okHttpClient) {
restTemplateBuilder.requestFactory(() -> new OkHttp3ClientHttpRequestFactory(okHttpClient));
LbRestTemplate restTemplate = restTemplateBuilder.build(LbRestTemplate.class);
restTemplate.getInterceptors().add(context.getBean(RestTemplateHeaderInterceptor.class));
configMessageConverters(context, restTemplate.getMessageConverters());
return restTemplate;
}
}
private static void configMessageConverters(ApplicationContext context, List<HttpMessageConverter<?>> converters) {
converters.removeIf(x -> x instanceof StringHttpMessageConverter || x instanceof MappingJackson2HttpMessageConverter);
converters.add(new StringHttpMessageConverter(Charsets.UTF_8));
converters.add(new MappingJackson2HttpMessageConverter(context.getBean(ObjectMapper.class)));
}
}

View File

@@ -0,0 +1,57 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.http;
import lombok.AllArgsConstructor;
import org.springblade.core.tool.constant.BladeConstant;
import org.springblade.core.tool.utils.ThreadLocalUtil;
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.NonNull;
import java.io.IOException;
/**
* RestTemplateHeaderInterceptor 传递Request header
*
* @author L.cm
*/
@AllArgsConstructor
public class RestTemplateHeaderInterceptor implements ClientHttpRequestInterceptor {
@NonNull
@Override
public ClientHttpResponse intercept(@NonNull HttpRequest request, @NonNull byte[] bytes, @NonNull ClientHttpRequestExecution execution) throws IOException {
HttpHeaders headers = ThreadLocalUtil.get(BladeConstant.CONTEXT_KEY);
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,86 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.cloud.http.client;
import org.springframework.http.HttpHeaders;
import org.springframework.http.StreamingHttpOutputMessage;
import org.springframework.http.client.AbstractClientHttpRequest;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.FastByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* Abstract base for {@link ClientHttpRequest} that also implement
* {@link StreamingHttpOutputMessage}. Ensures that headers and
* body are not written multiple times.
*
* @author Arjen Poutsma
* @since 6.1
*/
public abstract class AbstractStreamingClientHttpRequest extends AbstractClientHttpRequest
implements StreamingHttpOutputMessage {
@Nullable
private Body body;
@Nullable
private FastByteArrayOutputStream bodyStream;
@Override
protected final OutputStream getBodyInternal(HttpHeaders headers) {
Assert.state(this.body == null, "Invoke either getBody or setBody; not both");
if (this.bodyStream == null) {
this.bodyStream = new FastByteArrayOutputStream(1024);
}
return this.bodyStream;
}
@Override
public final void setBody(Body body) {
Assert.notNull(body, "Body must not be null");
assertNotExecuted();
Assert.state(this.bodyStream == null, "Invoke either getBody or setBody; not both");
this.body = body;
}
@Override
protected final ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
if (this.body == null && this.bodyStream != null) {
this.body = outputStream -> this.bodyStream.writeTo(outputStream);
}
return executeInternal(headers, this.body);
}
/**
* Abstract template method that writes the given headers and content to the HTTP request.
*
* @param headers the HTTP headers
* @param body the HTTP body, may be {@code null} if no body was {@linkplain #setBody(Body) set}
* @return the response object for the executed request
* @since 6.1
*/
protected abstract ClientHttpResponse executeInternal(HttpHeaders headers, @Nullable Body body) throws IOException;
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.cloud.http.client;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okio.BufferedSink;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.net.URI;
/**
* {@link ClientHttpRequest} implementation based on OkHttp 3.x.
*
* <p>Created via the {@link OkHttp3ClientHttpRequestFactory}.
*
* @author Luciano Leggieri
* @author Arjen Poutsma
* @author Roy Clarkson
* @since 4.3
*/
public class OkHttp3ClientHttpRequest extends AbstractStreamingClientHttpRequest {
private final OkHttpClient client;
private final URI uri;
private final HttpMethod method;
public OkHttp3ClientHttpRequest(OkHttpClient client, URI uri, HttpMethod method) {
this.client = client;
this.uri = uri;
this.method = method;
}
@Override
public HttpMethod getMethod() {
return this.method;
}
@Override
public URI getURI() {
return this.uri;
}
@Override
@SuppressWarnings("removal")
protected ClientHttpResponse executeInternal(HttpHeaders headers, @Nullable Body body) throws IOException {
RequestBody requestBody;
if (body != null) {
requestBody = new BodyRequestBody(headers, body);
} else if (okhttp3.internal.http.HttpMethod.requiresRequestBody(getMethod().name())) {
String header = headers.getFirst(HttpHeaders.CONTENT_TYPE);
MediaType contentType = (header != null) ? MediaType.parse(header) : null;
requestBody = RequestBody.create(contentType, new byte[0]);
} else {
requestBody = null;
}
Request.Builder builder = new Request.Builder()
.url(this.uri.toURL());
builder.method(this.method.name(), requestBody);
headers.forEach((headerName, headerValues) -> {
for (String headerValue : headerValues) {
builder.addHeader(headerName, headerValue);
}
});
Request request = builder.build();
return new OkHttp3ClientHttpResponse(this.client.newCall(request).execute());
}
private static class BodyRequestBody extends RequestBody {
private final HttpHeaders headers;
private final Body body;
public BodyRequestBody(HttpHeaders headers, Body body) {
this.headers = headers;
this.body = body;
}
@Override
public long contentLength() {
return this.headers.getContentLength();
}
@Nullable
@Override
public MediaType contentType() {
String contentType = this.headers.getFirst(HttpHeaders.CONTENT_TYPE);
if (StringUtils.hasText(contentType)) {
return MediaType.parse(contentType);
} else {
return null;
}
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
this.body.writeTo(sink.outputStream());
}
@Override
public boolean isOneShot() {
return !this.body.repeatable();
}
}
}

View File

@@ -0,0 +1,156 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.cloud.http.client;
import okhttp3.Cache;
import okhttp3.OkHttpClient;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.util.Assert;
import java.io.IOException;
import java.net.URI;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
/**
* {@link ClientHttpRequestFactory} implementation that uses
* <a href="https://square.github.io/okhttp/">OkHttp</a> 3.x to create requests.
*
* @author Luciano Leggieri
* @author Arjen Poutsma
* @author Roy Clarkson
* @since 4.3
*/
public class OkHttp3ClientHttpRequestFactory implements ClientHttpRequestFactory, DisposableBean {
private OkHttpClient client;
private final boolean defaultClient;
/**
* Create a factory with a default {@link OkHttpClient} instance.
*/
public OkHttp3ClientHttpRequestFactory() {
this.client = new OkHttpClient();
this.defaultClient = true;
}
/**
* Create a factory with the given {@link OkHttpClient} instance.
*
* @param client the client to use
*/
public OkHttp3ClientHttpRequestFactory(OkHttpClient client) {
Assert.notNull(client, "OkHttpClient must not be null");
this.client = client;
this.defaultClient = false;
}
/**
* Set the underlying read timeout in milliseconds.
* A value of 0 specifies an infinite timeout.
*/
public void setReadTimeout(int readTimeout) {
this.client = this.client.newBuilder()
.readTimeout(readTimeout, TimeUnit.MILLISECONDS)
.build();
}
/**
* Set the underlying read timeout in milliseconds.
* A value of 0 specifies an infinite timeout.
*
* @since 6.1
*/
public void setReadTimeout(Duration readTimeout) {
this.client = this.client.newBuilder()
.readTimeout(readTimeout)
.build();
}
/**
* Set the underlying write timeout in milliseconds.
* A value of 0 specifies an infinite timeout.
*/
public void setWriteTimeout(int writeTimeout) {
this.client = this.client.newBuilder()
.writeTimeout(writeTimeout, TimeUnit.MILLISECONDS)
.build();
}
/**
* Set the underlying write timeout in milliseconds.
* A value of 0 specifies an infinite timeout.
*
* @since 6.1
*/
public void setWriteTimeout(Duration writeTimeout) {
this.client = this.client.newBuilder()
.writeTimeout(writeTimeout)
.build();
}
/**
* Set the underlying connect timeout in milliseconds.
* A value of 0 specifies an infinite timeout.
*/
public void setConnectTimeout(int connectTimeout) {
this.client = this.client.newBuilder()
.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
.build();
}
/**
* Set the underlying connect timeout in milliseconds.
* A value of 0 specifies an infinite timeout.
*
* @since 6.1
*/
public void setConnectTimeout(Duration connectTimeout) {
this.client = this.client.newBuilder()
.connectTimeout(connectTimeout)
.build();
}
@NotNull
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) {
return new OkHttp3ClientHttpRequest(this.client, uri, httpMethod);
}
@Override
public void destroy() throws IOException {
if (this.defaultClient) {
// Clean up the client if we created it in the constructor
Cache cache = this.client.cache();
if (cache != null) {
cache.close();
}
this.client.dispatcher().executorService().shutdown();
this.client.connectionPool().evictAll();
}
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.cloud.http.client;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import java.io.IOException;
import java.io.InputStream;
/**
* {@link ClientHttpResponse} implementation based on OkHttp 3.x.
*
* @author Luciano Leggieri
* @author Arjen Poutsma
* @author Roy Clarkson
* @since 4.3
*/
public class OkHttp3ClientHttpResponse implements ClientHttpResponse {
private final Response response;
@Nullable
private volatile HttpHeaders headers;
public OkHttp3ClientHttpResponse(Response response) {
Assert.notNull(response, "Response must not be null");
this.response = response;
}
@Override
public HttpStatusCode getStatusCode() throws IOException {
return HttpStatusCode.valueOf(this.response.code());
}
@Override
public String getStatusText() {
return this.response.message();
}
@Override
public InputStream getBody() throws IOException {
ResponseBody body = this.response.body();
return (body != null ? body.byteStream() : InputStream.nullInputStream());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = this.headers;
if (headers == null) {
headers = new HttpHeaders();
for (String headerName : this.response.headers().names()) {
for (String headerValue : this.response.headers(headerName)) {
headers.add(headerName, headerValue);
}
}
this.headers = headers;
}
return headers;
}
@Override
public void close() {
ResponseBody body = this.response.body();
if (body != null) {
body.close();
}
}
}

View File

@@ -0,0 +1,266 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.http.logger;
import okhttp3.*;
import okhttp3.internal.http.HttpHeaders;
import okio.Buffer;
import okio.BufferedSource;
import okio.GzipSource;
import org.springblade.core.launch.log.BladeLogLevel;
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;
/**
* 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;
private final Logger logger;
private volatile BladeLogLevel level = BladeLogLevel.NONE;
public interface Logger {
/**
* log
* @param message message
*/
void log(String message);
}
public HttpLoggingInterceptor(Logger logger) {
this.logger = logger;
}
/**
* Change the level at which this interceptor logs.
* @param level log Level
* @return HttpLoggingInterceptor
*/
public HttpLoggingInterceptor setLevel(BladeLogLevel level) {
this.level = Objects.requireNonNull(level, "level == null. Use Level.NONE instead.");
return this;
}
public BladeLogLevel getLevel() {
return level;
}
@Override
public Response intercept(Chain chain) throws IOException {
BladeLogLevel level = this.level;
Request request = chain.request();
if (level == BladeLogLevel.NONE) {
return chain.proceed(request);
}
boolean logBody = level == BladeLogLevel.BODY;
boolean logHeaders = logBody || level == BladeLogLevel.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();
int count = headers.size();
for (int i = 0; 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.getBuffer();
Long gzippedLength = null;
if ("gzip".equalsIgnoreCase(headers.get("Content-Encoding"))) {
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 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 < 16; 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,42 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.http.logger;
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,26 @@
package org.springblade.core.cloud.sentinel;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* Sentinel统一限流策略
*
* @author Chill
*/
public class BladeBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
// Return 429 (Too Many Requests) by default.
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getWriter().print(JsonUtil.toJson(R.fail(e.getMessage())));
}
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.cloud.sentinel;
import com.alibaba.cloud.sentinel.feign.SentinelContractHolder;
import feign.Contract;
import feign.Feign;
import feign.InvocationHandlerFactory;
import feign.Target;
import lombok.SneakyThrows;
import org.springblade.core.cloud.feign.BladeFallbackFactory;
import org.springframework.beans.BeansException;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.FeignClientFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.StringUtils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
/**
* feign集成sentinel自动配置
* 重写 {@link com.alibaba.cloud.sentinel.feign.SentinelFeign} 适配最新API
*
* @author Chill
*/
public class BladeFeignSentinel {
public static Builder builder() {
return new Builder();
}
public static final class Builder extends Feign.Builder implements ApplicationContextAware {
private Contract contract = new Contract.Default();
private ApplicationContext applicationContext;
private FeignClientFactory feignContext;
@Override
public Feign.Builder invocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory) {
throw new UnsupportedOperationException();
}
@Override
public Builder contract(Contract contract) {
this.contract = contract;
return this;
}
@Override
public Feign internalBuild() {
super.invocationHandlerFactory(new InvocationHandlerFactory() {
@SneakyThrows
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
// 注解取值以避免循环依赖的问题
FeignClient feignClient = AnnotationUtils.findAnnotation(target.type(), FeignClient.class);
Class fallback = feignClient.fallback();
Class fallbackFactory = feignClient.fallbackFactory();
String contextId = feignClient.contextId();
if (!StringUtils.hasText(contextId)) {
contextId = feignClient.name();
}
Object fallbackInstance;
FallbackFactory fallbackFactoryInstance;
// 判断fallback类型
if (void.class != fallback) {
fallbackInstance = getFromContext(contextId, "fallback", fallback, target.type());
return new BladeSentinelInvocationHandler(target, dispatch, new FallbackFactory.Default(fallbackInstance));
}
if (void.class != fallbackFactory) {
fallbackFactoryInstance = (FallbackFactory) getFromContext(contextId, "fallbackFactory", fallbackFactory, FallbackFactory.class);
return new BladeSentinelInvocationHandler(target, dispatch, fallbackFactoryInstance);
}
// 默认fallbackFactory
BladeFallbackFactory bladeFallbackFactory = new BladeFallbackFactory(target);
return new BladeSentinelInvocationHandler(target, dispatch, bladeFallbackFactory);
}
private Object getFromContext(String name, String type, Class fallbackType, Class targetType) {
Object fallbackInstance = feignContext.getInstance(name, fallbackType);
if (fallbackInstance == null) {
throw new IllegalStateException(
String.format("No %s instance of type %s found for feign client %s",
type, fallbackType, name)
);
}
if (!targetType.isAssignableFrom(fallbackType)) {
throw new IllegalStateException(
String.format("Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
type, fallbackType, targetType, name)
);
}
return fallbackInstance;
}
});
super.contract(new SentinelContractHolder(contract));
return super.internalBuild();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
feignContext = this.applicationContext.getBean(FeignClientFactory.class);
}
}
}

View File

@@ -0,0 +1,71 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.cloud.sentinel;
import com.alibaba.cloud.sentinel.feign.SentinelFeignAutoConfiguration;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import feign.Feign;
import feign.RequestInterceptor;
import lombok.AllArgsConstructor;
import org.springblade.core.cloud.feign.BladeFeignRequestInterceptor;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
/**
* Sentinel配置类
*
* @author Chill
*/
@AllArgsConstructor
@AutoConfiguration(before = SentinelFeignAutoConfiguration.class)
@ConditionalOnProperty(name = "feign.sentinel.enabled")
public class BladeSentinelAutoConfiguration {
@Bean
@Primary
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Feign.Builder feignSentinelBuilder(RequestInterceptor requestInterceptor) {
return BladeFeignSentinel.builder().requestInterceptor(requestInterceptor);
}
@Bean
@ConditionalOnMissingBean(name = "bladeFeignRequestInterceptor")
public RequestInterceptor requestInterceptor() {
return new BladeFeignRequestInterceptor();
}
@Bean
@ConditionalOnMissingBean
public BlockExceptionHandler blockExceptionHandler() {
return new BladeBlockExceptionHandler();
}
}

View File

@@ -0,0 +1,82 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.cloud.sentinel;
import com.alibaba.cloud.sentinel.SentinelProperties;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelWebInterceptor;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.DefaultBlockExceptionHandler;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.UrlCleaner;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.SentinelWebMvcConfig;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.util.StringUtils;
import java.util.Optional;
/**
* 处理sentinel2021兼容问题
*
* @author Chill
*/
@RequiredArgsConstructor
@Import(BladeSentinelFilterConfiguration.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class BladeSentinelFilterConfiguration {
@Bean
public SentinelWebInterceptor sentinelWebInterceptor(SentinelWebMvcConfig sentinelWebMvcConfig) {
return new SentinelWebInterceptor(sentinelWebMvcConfig);
}
@Bean
public SentinelWebMvcConfig sentinelWebMvcConfig(SentinelProperties properties,
Optional<UrlCleaner> urlCleanerOptional, Optional<BlockExceptionHandler> blockExceptionHandlerOptional,
Optional<RequestOriginParser> requestOriginParserOptional) {
SentinelWebMvcConfig sentinelWebMvcConfig = new SentinelWebMvcConfig();
sentinelWebMvcConfig.setHttpMethodSpecify(properties.getHttpMethodSpecify());
sentinelWebMvcConfig.setWebContextUnify(properties.getWebContextUnify());
if (blockExceptionHandlerOptional.isPresent()) {
blockExceptionHandlerOptional.ifPresent(sentinelWebMvcConfig::setBlockExceptionHandler);
} else {
if (StringUtils.hasText(properties.getBlockPage())) {
sentinelWebMvcConfig.setBlockExceptionHandler(
((request, response, e) -> response.sendRedirect(properties.getBlockPage())));
} else {
sentinelWebMvcConfig.setBlockExceptionHandler(new DefaultBlockExceptionHandler());
}
}
urlCleanerOptional.ifPresent(sentinelWebMvcConfig::setUrlCleaner);
requestOriginParserOptional.ifPresent(sentinelWebMvcConfig::setOriginParser);
return sentinelWebMvcConfig;
}
}

View File

@@ -0,0 +1,178 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.cloud.sentinel;
import com.alibaba.cloud.sentinel.feign.SentinelContractHolder;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import feign.Feign;
import feign.InvocationHandlerFactory;
import feign.MethodMetadata;
import feign.Target;
import org.springframework.cloud.openfeign.FallbackFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.LinkedHashMap;
import java.util.Map;
import static feign.Util.checkNotNull;
/**
* 重写 {@link com.alibaba.cloud.sentinel.feign.SentinelInvocationHandler} 适配最新API
*
* @author Chill
*/
public class BladeSentinelInvocationHandler implements InvocationHandler {
private final Target<?> target;
private final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch;
private FallbackFactory fallbackFactory;
private Map<Method, Method> fallbackMethodMap;
public BladeSentinelInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch,
FallbackFactory fallbackFactory) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch");
this.fallbackFactory = fallbackFactory;
this.fallbackMethodMap = toFallbackMethod(dispatch);
}
public BladeSentinelInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch");
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
if ("equals".equals(method.getName())) {
try {
Object otherHandler = args.length > 0 && args[0] != null
? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
Object result;
InvocationHandlerFactory.MethodHandler methodHandler = this.dispatch.get(method);
// only handle by HardCodedTarget
if (target instanceof Target.HardCodedTarget) {
Target.HardCodedTarget hardCodedTarget = (Target.HardCodedTarget) target;
MethodMetadata methodMetadata = SentinelContractHolder.METADATA_MAP
.get(hardCodedTarget.type().getName()
+ Feign.configKey(hardCodedTarget.type(), method));
// resource default is HttpMethod:protocol://url
if (methodMetadata == null) {
result = methodHandler.invoke(args);
} else {
String resourceName = methodMetadata.template().method().toUpperCase()
+ ":" + hardCodedTarget.url() + methodMetadata.template().path();
Entry entry = null;
try {
ContextUtil.enter(resourceName);
entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
result = methodHandler.invoke(args);
} catch (Throwable ex) {
// fallback handle
if (!BlockException.isBlockException(ex)) {
Tracer.trace(ex);
}
if (fallbackFactory != null) {
try {
Object fallbackResult = fallbackMethodMap.get(method)
.invoke(fallbackFactory.create(ex), args);
return fallbackResult;
} catch (IllegalAccessException e) {
// shouldn't happen as method is public due to being an
// interface
throw new AssertionError(e);
} catch (InvocationTargetException e) {
throw new AssertionError(e.getCause());
}
} else {
// throw exception if fallbackFactory is null
throw ex;
}
} finally {
if (entry != null) {
entry.exit(1, args);
}
ContextUtil.exit();
}
}
} else {
// other target type using default strategy
result = methodHandler.invoke(args);
}
return result;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof BladeSentinelInvocationHandler) {
BladeSentinelInvocationHandler other = (BladeSentinelInvocationHandler) obj;
return target.equals(other.target);
}
return false;
}
@Override
public int hashCode() {
return target.hashCode();
}
@Override
public String toString() {
return target.toString();
}
static Map<Method, Method> toFallbackMethod(Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
Map<Method, Method> result = new LinkedHashMap<>();
for (Method method : dispatch.keySet()) {
method.setAccessible(true);
result.put(method, method);
}
return result;
}
}

View File

@@ -0,0 +1,53 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.server;
import io.undertow.Undertow;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import static io.undertow.UndertowOptions.ENABLE_HTTP2;
/**
* Undertow http2 h2c 配置,对 servlet 开启
*
* @author L.cm
*/
@AutoConfiguration(before = ServletWebServerFactoryAutoConfiguration.class)
@ConditionalOnClass(Undertow.class)
public class UndertowHttp2Configuration {
@Bean
public WebServerFactoryCustomizer<UndertowServletWebServerFactory> undertowHttp2WebServerFactoryCustomizer() {
return factory -> factory.addBuilderCustomizers(builder -> builder.setServerOption(ENABLE_HTTP2, true));
}
}

View File

@@ -0,0 +1,58 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
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,113 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
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 versionMediaTypes = new BladeMediaType(apiVersion.value()).toString();
mappingInfoBuilder.produces(versionMediaTypes);
}
return mappingInfoBuilder.options(super.getBuilderConfiguration()).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,110 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.version;
import feign.MethodMetadata;
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.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,53 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
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,47 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.version;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.context.annotation.Bean;
/**
* url版本号处理
*
* 参考https://gitee.com/lianqu1990/spring-boot-starter-version-mapping
*
* @author L.cm
*/
@AutoConfiguration
@ConditionalOnWebApplication
public class VersionMappingAutoConfiguration {
@Bean
public WebMvcRegistrations bladeWebMvcRegistrations() {
return new BladeWebMvcRegistrations();
}
}