fix bugs
This commit is contained in:
54
blade-core-cloud/pom.xml
Normal file
54
blade-core-cloud/pom.xml
Normal 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>
|
||||
@@ -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 "";
|
||||
|
||||
}
|
||||
@@ -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 "";
|
||||
}
|
||||
@@ -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 "";
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
// 暂时不支持 flux,rx,异步等,返回值不是 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);
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {};
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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())));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 Types,application/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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user