feat: 初始化项目,迁移原代码,正式开源
This commit is contained in:
commit
8fcb802cfc
40
.gitignore
vendored
Normal file
40
.gitignore
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
.idea
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
13
README.md
Normal file
13
README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# REST请求代理(Http Interface)
|
||||
|
||||
这可能是你用过最爽的http对接神器。
|
||||
|
||||
## 特性列表
|
||||
- 无感知自动注入,只需interface + 注解即可轻松完成对接
|
||||
- 优雅的API,省去了您的学习烦恼
|
||||
- 高性能实现,智能结果处理绑定
|
||||
- 支持文件上传下载无感对接
|
||||
- 自动解包结果包装类
|
||||
- 自动转换参数、请求体
|
||||
- 自定义鉴权(施工中)
|
||||
- 动态替换实现(施工中)
|
89
pom.xml
Normal file
89
pom.xml
Normal file
@ -0,0 +1,89 @@
|
||||
<?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>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.7.7</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
<groupId>group.flyfish</groupId>
|
||||
<artifactId>rest-proxy</artifactId>
|
||||
<version>1.0.0</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<java.version>1.8</java.version>
|
||||
<fastjson.version>2.0.22</fastjson.version>
|
||||
<commons-collection.version>4.4</commons-collection.version>
|
||||
<commons.lang.version>2.6</commons.lang.version>
|
||||
<sdk.version>1.0.0</sdk.version>
|
||||
</properties>
|
||||
|
||||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>rest-proxy-api</module>
|
||||
<module>rest-proxy-core</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-collections4</artifactId>
|
||||
<version>${commons-collection.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.reflections</groupId>
|
||||
<artifactId>reflections</artifactId>
|
||||
<version>0.10.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>${fastjson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>group.flyfish</groupId>
|
||||
<artifactId>rest-proxy-api</artifactId>
|
||||
<version>${sdk.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
21
rest-proxy-api/pom.xml
Normal file
21
rest-proxy-api/pom.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>group.flyfish</groupId>
|
||||
<artifactId>rest-proxy</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>rest-proxy-api</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,21 @@
|
||||
package group.flyfish.rest.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 自动解包,意味着错误时自动抛出异常
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface AutoMapping {
|
||||
|
||||
/**
|
||||
* 结果映射器
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
Class<?> value() default Object.class;
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package group.flyfish.rest.annotation;
|
||||
|
||||
import group.flyfish.rest.enums.HttpMethod;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 启用Rest请求的方法会自动代理实现,
|
||||
* 并封装返回值
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface RestApi {
|
||||
|
||||
/**
|
||||
* uri的别名
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
@AliasFor("uri")
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* 请求方法
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
HttpMethod method() default HttpMethod.GET;
|
||||
|
||||
/**
|
||||
* 多个参数时使用合并的body
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
boolean mergedBody() default false;
|
||||
|
||||
/**
|
||||
* 可选指定的url,不会从默认地址请求
|
||||
*
|
||||
* @return url
|
||||
*/
|
||||
String url() default "";
|
||||
|
||||
/**
|
||||
* 请求uri,使用次标注必须指定BaseUrl或者配置(现在还不支持)
|
||||
*
|
||||
* @return uri
|
||||
*/
|
||||
@AliasFor("value")
|
||||
String uri() default "";
|
||||
|
||||
/**
|
||||
* 基本路径,包含host
|
||||
*
|
||||
* @return baseUrl
|
||||
*/
|
||||
String baseUrl() default "";
|
||||
|
||||
/**
|
||||
* 是否带上认证token
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
boolean credentials() default false;
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package group.flyfish.rest.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* rest请求体标记
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface RestBody {
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package group.flyfish.rest.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* rest请求头标记
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface RestHeader {
|
||||
|
||||
/**
|
||||
* 头的名称
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package group.flyfish.rest.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 请求参数变量注解
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface RestParam {
|
||||
|
||||
/**
|
||||
* 显式指定变量名,防止类型名擦除
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package group.flyfish.rest.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 注解一个map或者一个对象,将所有值作为参数表传入
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface RestParams {
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package group.flyfish.rest.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 路径变量注解
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface RestPathParam {
|
||||
|
||||
/**
|
||||
* 显式指定变量名,防止类型名擦除
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package group.flyfish.rest.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 标记服务为rest proxy
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface RestService {
|
||||
|
||||
/**
|
||||
* 通过标识符找到地址,不需要#开头
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* 服务级别的基本url,字典请使用#开头
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
String baseUrl() default "";
|
||||
|
||||
/**
|
||||
* 超时时间,-1则取默认,0表示无限
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
long timeout() default -1L;
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package group.flyfish.rest.enums;
|
||||
|
||||
/**
|
||||
* Http请求类型
|
||||
* @author wangyu
|
||||
*/
|
||||
public enum HttpMethod {
|
||||
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package group.flyfish.rest.enums;
|
||||
|
||||
/**
|
||||
* 响应类型
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public enum ResponseType {
|
||||
NORMAL, TEXT, JSON, BINARY, OBJECT
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package group.flyfish.rest.mapping;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* rest请求的结果映射
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public interface RestResultMapping {
|
||||
|
||||
/**
|
||||
* 注册了的映射
|
||||
*/
|
||||
Map<Class<?>, RestResultMapping> MAPPINGS = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 模糊的结果映射
|
||||
*
|
||||
* @param result 结果
|
||||
* @param <T> 泛型
|
||||
* @return 映射后的结果
|
||||
*/
|
||||
<T> T map(Object result);
|
||||
|
||||
/**
|
||||
* 解析返回类型
|
||||
*
|
||||
* @param resultType 返回类型
|
||||
* @return 结果
|
||||
*/
|
||||
Type resolve(Type resultType);
|
||||
}
|
90
rest-proxy-core/pom.xml
Normal file
90
rest-proxy-core/pom.xml
Normal file
@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>group.flyfish</groupId>
|
||||
<artifactId>rest-proxy</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>rest-proxy-core</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>group.flyfish</groupId>
|
||||
<artifactId>rest-proxy-api</artifactId>
|
||||
<version>${sdk.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.reflections</groupId>
|
||||
<artifactId>reflections</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpmime</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<testResources>
|
||||
<testResource>
|
||||
<directory>${project.basedir}/src/test/resources</directory>
|
||||
<excludes>
|
||||
<exclude>dev/*</exclude>
|
||||
<exclude>prod/*</exclude>
|
||||
</excludes>
|
||||
</testResource>
|
||||
<testResource>
|
||||
<directory>${project.basedir}/src/test/resources/${config.dir}</directory>
|
||||
</testResource>
|
||||
</testResources>
|
||||
</build>
|
||||
</project>
|
@ -0,0 +1,25 @@
|
||||
package group.flyfish.rest.annotation;
|
||||
|
||||
import group.flyfish.rest.configuration.RestClientConfiguration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 启用restapi自动代理
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Import(RestClientConfiguration.class)
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface EnableRestApiProxy {
|
||||
|
||||
/**
|
||||
* 基本扫描路径
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
String[] basePackages() default "group.flyfish.rest";
|
||||
}
|
@ -0,0 +1,323 @@
|
||||
package group.flyfish.rest.client;
|
||||
|
||||
import group.flyfish.rest.core.builder.RestClientBuilder;
|
||||
import group.flyfish.rest.core.exception.RestClientException;
|
||||
import group.flyfish.rest.core.produce.HttpClientProducer;
|
||||
import group.flyfish.rest.enums.ResponseType;
|
||||
import group.flyfish.rest.utils.JacksonUtil;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.StatusLine;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpRequestBase;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static group.flyfish.rest.constants.RestConstants.DEFAULT_EXECUTOR;
|
||||
|
||||
/**
|
||||
* Rest请求客户端
|
||||
*
|
||||
* @author Mr.Wang
|
||||
* <p>
|
||||
* @apiNote 1. 全builder调用,用户系统内部相互通信
|
||||
* 2. 支持异步回调
|
||||
* 3. 多样性组合
|
||||
* 4. 解耦实现
|
||||
* 5. 支持上传文件、FormData,JSON、支持自定义无侵入扩展
|
||||
* 6. 新增单例httpClient模式,复用连接让应用更高效
|
||||
*/
|
||||
@Slf4j
|
||||
public final class RestClient {
|
||||
|
||||
private static volatile CloseableHttpClient client;
|
||||
private final HttpRequestBase request;
|
||||
private boolean async = false;
|
||||
private Consumer<HttpEntity> consumer;
|
||||
private Consumer<RestClientException> errorConsumer;
|
||||
private ExecutorService executorService;
|
||||
private ResponseType responseType = ResponseType.NORMAL;
|
||||
private Class<?> resultClass;
|
||||
private TypeReference<?> typeReference;
|
||||
private JavaType resultType;
|
||||
|
||||
/**
|
||||
* 内部构造方法,不对外公开
|
||||
*
|
||||
* @param request 请求信息
|
||||
*/
|
||||
public RestClient(HttpRequestBase request) {
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置单例的客户端
|
||||
*
|
||||
* @param client 客户端
|
||||
*/
|
||||
public static void setClient(CloseableHttpClient client) {
|
||||
RestClient.client = client;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增一个构建器
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
public static RestClientBuilder create() {
|
||||
return new RestClientBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置请求失败时的回调
|
||||
*
|
||||
* @param errorConsumer 错误回调
|
||||
* @return 结果
|
||||
*/
|
||||
public RestClient onError(Consumer<RestClientException> errorConsumer) {
|
||||
this.errorConsumer = errorConsumer;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误处理
|
||||
*
|
||||
* @param e 异常
|
||||
*/
|
||||
private void handleError(RestClientException e) {
|
||||
if (null != errorConsumer) {
|
||||
errorConsumer.accept(e);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置响应类型
|
||||
*
|
||||
* @param responseType 响应类型
|
||||
* @return 结果
|
||||
*/
|
||||
public RestClient responseType(ResponseType responseType) {
|
||||
this.responseType = responseType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记线程池执行
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
public RestClient async() {
|
||||
this.async = true;
|
||||
this.executorService = DEFAULT_EXECUTOR;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记指定线程池执行
|
||||
*
|
||||
* @param executorService 线程池
|
||||
* @return 结果
|
||||
*/
|
||||
public RestClient async(ExecutorService executorService) {
|
||||
this.async = true;
|
||||
this.executorService = executorService;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步执行,接收结果
|
||||
*
|
||||
* @param consumer 结果
|
||||
*/
|
||||
public void execute(Consumer<HttpEntity> consumer) {
|
||||
this.consumer = consumer;
|
||||
if (this.async) {
|
||||
if (this.executorService == null) {
|
||||
handleError(new RestClientException("线程池未指定或为空!"));
|
||||
}
|
||||
this.executorService.submit(this::executeSafety);
|
||||
} else {
|
||||
executeSafety();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 静默执行,抛弃全部异常
|
||||
*/
|
||||
public void executeSilent() {
|
||||
executeSafety();
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行请求,返回Map
|
||||
*
|
||||
* @return map
|
||||
* @throws IOException 异常
|
||||
*/
|
||||
public Map<String, Object> executeForMap() throws IOException {
|
||||
this.responseType = ResponseType.JSON;
|
||||
return innerExecute();
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行请求,返回字符串
|
||||
*
|
||||
* @return 字符串
|
||||
* @throws IOException 异常
|
||||
*/
|
||||
public String executeForString() throws IOException {
|
||||
this.responseType = ResponseType.TEXT;
|
||||
return innerExecute();
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全的执行
|
||||
*/
|
||||
private void executeSafety() {
|
||||
try {
|
||||
execute();
|
||||
} catch (IOException e) {
|
||||
handleError(new RestClientException(e.getMessage(), e, null));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行并序列化,该方法会安全的返回对象或者空
|
||||
*
|
||||
* @param clazz 类
|
||||
* @param <T> 泛型
|
||||
* @return 结果
|
||||
*/
|
||||
@Nullable
|
||||
public <T> T execute(Class<T> clazz) {
|
||||
this.responseType = ResponseType.OBJECT;
|
||||
this.resultClass = clazz;
|
||||
try {
|
||||
return innerExecute();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行并序列化,使用复杂的自动构造的类型
|
||||
*
|
||||
* @param type jackson的强化类型
|
||||
* @param <T> 泛型
|
||||
* @return 结果
|
||||
*/
|
||||
@Nullable
|
||||
public <T> T execute(JavaType type) {
|
||||
this.responseType = ResponseType.OBJECT;
|
||||
this.resultType = type;
|
||||
try {
|
||||
return innerExecute();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public <T> T execute(TypeReference<T> typeReference) {
|
||||
this.responseType = ResponseType.OBJECT;
|
||||
this.typeReference = typeReference;
|
||||
try {
|
||||
return innerExecute();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行请求,返回响应实体,自行处理
|
||||
*
|
||||
* @return 响应实体
|
||||
* @throws IOException 异常
|
||||
*/
|
||||
public <T> T execute() throws IOException {
|
||||
return innerExecute();
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部执行方法,预处理结果
|
||||
*
|
||||
* @param <T> 泛型
|
||||
* @return 结果
|
||||
*/
|
||||
private <T> T innerExecute() throws IOException {
|
||||
if (null == client) {
|
||||
client = HttpClientProducer.produce();
|
||||
}
|
||||
log.info("【Rest Invoke】{} {}", request.getMethod(), request.getURI());
|
||||
try (CloseableHttpResponse response = client.execute(request)) {
|
||||
StatusLine statusLine = response.getStatusLine();
|
||||
if (200 == statusLine.getStatusCode()) {
|
||||
HttpEntity entity = response.getEntity();
|
||||
if (consumer != null) {
|
||||
consumer.accept(entity);
|
||||
}
|
||||
return resolveResponse(entity);
|
||||
} else {
|
||||
int requestCode = statusLine.getStatusCode();
|
||||
log.error(request.getURI() + "接口调用失败,code:" + requestCode);
|
||||
handleError(new RestClientException("网络请求状态异常!代码:" + requestCode, null, statusLine));
|
||||
}
|
||||
} catch (UnknownHostException e) {
|
||||
handleError(new RestClientException(e.getMessage(), e, null));
|
||||
log.error("未知的请求地址!");
|
||||
} finally {
|
||||
request.releaseConnection();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析结果
|
||||
*
|
||||
* @param entity 响应体
|
||||
* @param <T> 泛型
|
||||
* @return 结果
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T resolveResponse(HttpEntity entity) throws IOException {
|
||||
switch (responseType) {
|
||||
case TEXT:
|
||||
return (T) EntityUtils.toString(entity);
|
||||
case JSON:
|
||||
return (T) JacksonUtil.json2Map(EntityUtils.toString(entity));
|
||||
case BINARY:
|
||||
try (InputStream in = entity.getContent()) {
|
||||
return (T) StreamUtils.copyToByteArray(in);
|
||||
}
|
||||
case OBJECT:
|
||||
if (null != this.resultClass) {
|
||||
return (T) JacksonUtil.fromJson(EntityUtils.toString(entity), this.resultClass);
|
||||
}
|
||||
if (null != this.typeReference) {
|
||||
return (T) JacksonUtil.fromJson(EntityUtils.toString(entity), this.typeReference);
|
||||
}
|
||||
if (null != this.resultType) {
|
||||
return (T) JacksonUtil.fromJson(EntityUtils.toString(entity), this.resultType);
|
||||
}
|
||||
default:
|
||||
return (T) entity;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package group.flyfish.rest.configuration;
|
||||
|
||||
import group.flyfish.rest.annotation.EnableRestApiProxy;
|
||||
import group.flyfish.rest.mapping.RestResultMapping;
|
||||
import group.flyfish.rest.registry.RestApiRegistry;
|
||||
import group.flyfish.rest.registry.proxy.support.RestArgumentResolver;
|
||||
import group.flyfish.rest.registry.proxy.support.RestArgumentResolverComposite;
|
||||
import group.flyfish.rest.registry.proxy.support.resolvers.RestBodyArgumentResolver;
|
||||
import group.flyfish.rest.registry.proxy.support.resolvers.RestHeaderArgumentResolver;
|
||||
import group.flyfish.rest.registry.proxy.support.resolvers.RestParamArgumentResolver;
|
||||
import group.flyfish.rest.registry.proxy.support.resolvers.RestPathParamArgumentResolver;
|
||||
import group.flyfish.rest.utils.DataUtils;
|
||||
import group.flyfish.rest.utils.IocBeans;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
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.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* rest请求相关配置
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@EnableConfigurationProperties(RestClientProperties.class)
|
||||
public class RestClientConfiguration {
|
||||
|
||||
/**
|
||||
* 注册rest自动代理
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
@Bean
|
||||
@Lazy
|
||||
public RestApiRegistry restApiRegistry(RestArgumentResolverComposite composite,
|
||||
List<RestResultMapping> mappings) {
|
||||
// 先注册映射们
|
||||
if (DataUtils.isNotEmpty(mappings)) {
|
||||
mappings.forEach(mapping -> RestResultMapping.MAPPINGS.put(mapping.getClass(), mapping));
|
||||
}
|
||||
// 最后实例化
|
||||
return new RestApiRegistry(composite);
|
||||
}
|
||||
|
||||
/**
|
||||
* IOC容器操作
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(name = "iocBeans")
|
||||
public IocBeans iocBeans() {
|
||||
return new IocBeans();
|
||||
}
|
||||
|
||||
/**
|
||||
* 一个很重要的bean,反向解析各种参数
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
@Bean
|
||||
public RestArgumentResolverComposite restArgumentResolverComposite() {
|
||||
List<RestArgumentResolver> resolvers = Arrays.asList(
|
||||
new RestPathParamArgumentResolver(),
|
||||
new RestBodyArgumentResolver(),
|
||||
new RestHeaderArgumentResolver(),
|
||||
new RestParamArgumentResolver()
|
||||
);
|
||||
return new RestArgumentResolverComposite(resolvers);
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package group.flyfish.rest.configuration;
|
||||
|
||||
import group.flyfish.rest.registry.RestApiRegistry;
|
||||
import group.flyfish.rest.utils.DataUtils;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 客户端的配置
|
||||
*
|
||||
* @author wangyu
|
||||
* 整合入spring boot,简化配置,提高业务复用性
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "rest.client", ignoreUnknownFields = false)
|
||||
@Slf4j
|
||||
public class RestClientProperties implements InitializingBean {
|
||||
|
||||
@Resource
|
||||
private RestApiRegistry restApiRegistry;
|
||||
|
||||
/**
|
||||
* 超时时间,默认30s
|
||||
*/
|
||||
private Duration connectionTimeout = Duration.ofSeconds(30);
|
||||
|
||||
/**
|
||||
* 基本url
|
||||
*/
|
||||
private String baseUrl;
|
||||
|
||||
/**
|
||||
* ssl无条件新人
|
||||
*/
|
||||
private Boolean alwaysTrust = true;
|
||||
|
||||
/**
|
||||
* 定义的内容字典,可以支持动态取值,使用#variable
|
||||
*/
|
||||
private Map<String, String> urls = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 获取字典url
|
||||
*
|
||||
* @param key 键
|
||||
* @return 结果
|
||||
*/
|
||||
public String getDictUrl(String key) {
|
||||
if (DataUtils.isEmpty(urls)) {
|
||||
return null;
|
||||
}
|
||||
return urls.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
log.info("initialized rest client properties。 baseUrl is:{}", this.baseUrl);
|
||||
restApiRegistry.initialize(this);
|
||||
}
|
||||
}
|
@ -0,0 +1,209 @@
|
||||
package group.flyfish.rest.constants;
|
||||
|
||||
import group.flyfish.rest.core.ThreadPoolManager;
|
||||
import group.flyfish.rest.core.builder.TypedMapBuilder;
|
||||
import group.flyfish.rest.core.resolver.*;
|
||||
import group.flyfish.rest.enums.HttpMethod;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
/**
|
||||
* rest客户端需要的常量都在这里
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public interface RestConstants {
|
||||
/**
|
||||
* 请求配置
|
||||
*/
|
||||
RequestConfig REQUEST_CONFIG = RequestConfig.custom().setConnectTimeout(3000).build();
|
||||
/**
|
||||
* 请求解析器
|
||||
*/
|
||||
Map<HttpMethod, HttpMethodResolver> RESOLVER_MAP = resolverBuilder()
|
||||
.with(HttpMethod.GET, new HttpGetResolver())
|
||||
.with(HttpMethod.POST, new HttpPostResolver())
|
||||
.with(HttpMethod.PUT, new HttpPutResolver())
|
||||
.with(HttpMethod.PATCH, new HttpPatchResolver())
|
||||
.with(HttpMethod.DELETE, new HttpDeleteResolver())
|
||||
.build();
|
||||
/**
|
||||
* 线程池
|
||||
*/
|
||||
ExecutorService DEFAULT_EXECUTOR = ThreadPoolManager.defaultCachedThreadPool();
|
||||
/**
|
||||
* 使用的MIME,后缀映射
|
||||
* 自定义的MIME TYPE,可以过滤
|
||||
*/
|
||||
Map<String, String> MIME_MAP = TypedMapBuilder.stringMapBuilder()
|
||||
.with("ai", "application/postscript")
|
||||
.with("aif", "audio/x-aiff")
|
||||
.with("aifc", "audio/x-aiff")
|
||||
.with("aiff", "audio/x-aiff")
|
||||
.with("asc", "text/plain")
|
||||
.with("au", "audio/basic")
|
||||
.with("avi", "video/x-msvideo")
|
||||
.with("bcpio", "application/x-bcpio")
|
||||
.with("bin", "application/octet-stream")
|
||||
.with("c", "text/plain")
|
||||
.with("cc", "text/plain")
|
||||
.with("ccad", "application/clariscad")
|
||||
.with("cdf", "application/x-netcdf")
|
||||
.with("class", "application/octet-stream")
|
||||
.with("cpio", "application/x-cpio")
|
||||
.with("cpt", "application/mac-compactpro")
|
||||
.with("csh", "application/x-csh")
|
||||
.with("css", "text/css")
|
||||
.with("dcr", "application/x-director")
|
||||
.with("dir", "application/x-director")
|
||||
.with("dms", "application/octet-stream")
|
||||
.with("doc", "application/msword")
|
||||
.with("drw", "application/drafting")
|
||||
.with("dvi", "application/x-dvi")
|
||||
.with("dwg", "application/acad")
|
||||
.with("dxf", "application/dxf")
|
||||
.with("dxr", "application/x-director")
|
||||
.with("eps", "application/postscript")
|
||||
.with("etx", "text/x-setext")
|
||||
.with("exe", "application/octet-stream")
|
||||
.with("ez", "application/andrew-inset")
|
||||
.with("f", "text/plain")
|
||||
.with("f90", "text/plain")
|
||||
.with("fli", "video/x-fli")
|
||||
.with("gif", "image/gif")
|
||||
.with("gtar", "application/x-gtar")
|
||||
.with("gz", "application/x-gzip")
|
||||
.with("h", "text/plain")
|
||||
.with("hdf", "application/x-hdf")
|
||||
.with("hh", "text/plain")
|
||||
.with("hqx", "application/mac-binhex40")
|
||||
.with("htm", "text/html")
|
||||
.with("html", "text/html")
|
||||
.with("ice", "x-conference/x-cooltalk")
|
||||
.with("ief", "image/ief")
|
||||
.with("iges", "model/iges")
|
||||
.with("igs", "model/iges")
|
||||
.with("ips", "application/x-ipscript")
|
||||
.with("ipx", "application/x-ipix")
|
||||
.with("jpe", "image/jpeg")
|
||||
.with("jpeg", "image/jpeg")
|
||||
.with("jpg", "image/jpeg")
|
||||
.with("js", "application/x-javascript")
|
||||
.with("kar", "audio/midi")
|
||||
.with("latex", "application/x-latex")
|
||||
.with("lha", "application/octet-stream")
|
||||
.with("lsp", "application/x-lisp")
|
||||
.with("lzh", "application/octet-stream")
|
||||
.with("m", "text/plain")
|
||||
.with("man", "application/x-troff-man")
|
||||
.with("me", "application/x-troff-me")
|
||||
.with("mesh", "model/mesh")
|
||||
.with("mid", "audio/midi")
|
||||
.with("midi", "audio/midi")
|
||||
.with("mif", "application/vnd.mif")
|
||||
.with("mime", "www/mime")
|
||||
.with("mov", "video/quicktime")
|
||||
.with("movie", "video/x-sgi-movie")
|
||||
.with("mp2", "audio/mpeg")
|
||||
.with("mp3", "audio/mpeg")
|
||||
.with("mp4", "video/mpeg")
|
||||
.with("mpe", "video/mpeg")
|
||||
.with("mpeg", "video/mpeg")
|
||||
.with("mpg", "video/mpeg")
|
||||
.with("mpga", "audio/mpeg")
|
||||
.with("ms", "application/x-troff-ms")
|
||||
.with("msh", "model/mesh")
|
||||
.with("nc", "application/x-netcdf")
|
||||
.with("oda", "application/oda")
|
||||
.with("pbm", "image/x-portable-bitmap")
|
||||
.with("pdb", "chemical/x-pdb")
|
||||
.with("pdf", "application/pdf")
|
||||
.with("pgm", "image/x-portable-graymap")
|
||||
.with("pgn", "application/x-chess-pgn")
|
||||
.with("png", "image/png")
|
||||
.with("pnm", "image/x-portable-anymap")
|
||||
.with("pot", "application/mspowerpoint")
|
||||
.with("ppm", "image/x-portable-pixmap")
|
||||
.with("pps", "application/mspowerpoint")
|
||||
.with("ppt", "application/mspowerpoint")
|
||||
.with("ppz", "application/mspowerpoint")
|
||||
.with("pre", "application/x-freelance")
|
||||
.with("prt", "application/pro_eng")
|
||||
.with("ps", "application/postscript")
|
||||
.with("qt", "video/quicktime")
|
||||
.with("ra", "audio/x-realaudio")
|
||||
.with("ram", "audio/x-pn-realaudio")
|
||||
.with("ras", "image/cmu-raster")
|
||||
.with("rgb", "image/x-rgb")
|
||||
.with("rm", "audio/x-pn-realaudio")
|
||||
.with("roff", "application/x-troff")
|
||||
.with("rpm", "audio/x-pn-realaudio-plugin")
|
||||
.with("rtf", "text/rtf")
|
||||
.with("rtx", "text/richtext")
|
||||
.with("scm", "application/x-lotusscreencam")
|
||||
.with("set", "application/set")
|
||||
.with("sgm", "text/sgml")
|
||||
.with("sgml", "text/sgml")
|
||||
.with("sh", "application/x-sh")
|
||||
.with("shar", "application/x-shar")
|
||||
.with("silo", "model/mesh")
|
||||
.with("sit", "application/x-stuffit")
|
||||
.with("skd", "application/x-koan")
|
||||
.with("skm", "application/x-koan")
|
||||
.with("skp", "application/x-koan")
|
||||
.with("skt", "application/x-koan")
|
||||
.with("smi", "application/smil")
|
||||
.with("smil", "application/smil")
|
||||
.with("snd", "audio/basic")
|
||||
.with("sol", "application/solids")
|
||||
.with("spl", "application/x-futuresplash")
|
||||
.with("src", "application/x-wais-source")
|
||||
.with("step", "application/STEP")
|
||||
.with("stl", "application/SLA")
|
||||
.with("stp", "application/STEP")
|
||||
.with("sv4cpio", "application/x-sv4cpio")
|
||||
.with("sv4crc", "application/x-sv4crc")
|
||||
.with("swf", "application/x-shockwave-flash")
|
||||
.with("t", "application/x-troff")
|
||||
.with("tar", "application/x-tar")
|
||||
.with("tcl", "application/x-tcl")
|
||||
.with("tex", "application/x-tex")
|
||||
.with("texi", "application/x-texinfo")
|
||||
.with("texinfo", "application/x-texinfo")
|
||||
.with("tif", "image/tiff")
|
||||
.with("tiff", "image/tiff")
|
||||
.with("tr", "application/x-troff")
|
||||
.with("tsi", "audio/TSP-audio")
|
||||
.with("tsp", "application/dsptype")
|
||||
.with("tsv", "text/tab-separated-values")
|
||||
.with("txt", "text/plain")
|
||||
.with("unv", "application/i-deas")
|
||||
.with("ustar", "application/x-ustar")
|
||||
.with("vcd", "application/x-cdlink")
|
||||
.with("vda", "application/vda")
|
||||
.with("viv", "video/vnd.vivo")
|
||||
.with("vivo", "video/vnd.vivo")
|
||||
.with("vrml", "model/vrml")
|
||||
.with("wav", "audio/x-wav")
|
||||
.with("wrl", "model/vrml")
|
||||
.with("xbm", "image/x-xbitmap")
|
||||
.with("xlc", "application/vnd.ms-excel")
|
||||
.with("xll", "application/vnd.ms-excel")
|
||||
.with("xlm", "application/vnd.ms-excel")
|
||||
.with("xls", "application/vnd.ms-excel")
|
||||
.with("xlw", "application/vnd.ms-excel")
|
||||
.with("xml", "text/xml")
|
||||
.with("xpm", "image/x-xpixmap")
|
||||
.with("xwd", "image/x-xwindowdump")
|
||||
.with("xyz", "chemical/x-pdb")
|
||||
.with("zip", "application/zip ")
|
||||
.with("apk", "application/vnd.android.package-archive")
|
||||
.with("*", "application/octet-stream")
|
||||
.build();
|
||||
|
||||
static TypedMapBuilder<HttpMethod, HttpMethodResolver> resolverBuilder() {
|
||||
return TypedMapBuilder.builder();
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package group.flyfish.rest.core;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* 线程池管理
|
||||
*
|
||||
* @author wangyu
|
||||
* 用于管理Http异步执行池
|
||||
*/
|
||||
public class ThreadPoolManager {
|
||||
|
||||
public static ExecutorService defaultCachedThreadPool() {
|
||||
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
|
||||
60L, TimeUnit.SECONDS,
|
||||
new SynchronousQueue<>(),
|
||||
DefaultThreadFactory.createDefault());
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认的线程工厂
|
||||
*/
|
||||
private static class DefaultThreadFactory implements ThreadFactory {
|
||||
private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1);
|
||||
private final ThreadGroup group;
|
||||
private final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||
private final String namePrefix;
|
||||
|
||||
private DefaultThreadFactory() {
|
||||
SecurityManager s = System.getSecurityManager();
|
||||
group = (s != null) ? s.getThreadGroup() :
|
||||
Thread.currentThread().getThreadGroup();
|
||||
namePrefix = "pool-" +
|
||||
POOL_NUMBER.getAndIncrement() +
|
||||
"-thread-";
|
||||
}
|
||||
|
||||
private static DefaultThreadFactory createDefault() {
|
||||
return new DefaultThreadFactory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread t = new Thread(group, r,
|
||||
namePrefix + threadNumber.getAndIncrement(),
|
||||
0);
|
||||
if (t.isDaemon()) {
|
||||
t.setDaemon(false);
|
||||
}
|
||||
if (t.getPriority() != Thread.NORM_PRIORITY) {
|
||||
t.setPriority(Thread.NORM_PRIORITY);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,166 @@
|
||||
package group.flyfish.rest.core.builder;
|
||||
|
||||
import group.flyfish.rest.utils.DataUtils;
|
||||
import group.flyfish.rest.utils.JacksonUtil;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Map参数构造器
|
||||
*
|
||||
* @author Mr.Wang
|
||||
* @apiNote 通过重载过滤已知类型的空值,并分别处理
|
||||
*/
|
||||
public final class MapParamBuilder {
|
||||
|
||||
private static final String PAGE_KEY = "pageUtil";
|
||||
|
||||
private static final String EMPTY_PATTERN = "{}";
|
||||
|
||||
private Map<String, Object> params;
|
||||
|
||||
private MapParamBuilder() {
|
||||
this.params = new HashMap<>();
|
||||
}
|
||||
|
||||
public static MapParamBuilder builder() {
|
||||
return new MapParamBuilder();
|
||||
}
|
||||
|
||||
public static MapParamBuilder of(Map<String, Object> initialParams) {
|
||||
MapParamBuilder builder = new MapParamBuilder();
|
||||
builder.withAll(initialParams);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static MapParamBuilder empty() {
|
||||
MapParamBuilder builder = new MapParamBuilder();
|
||||
builder.params = Collections.emptyMap();
|
||||
return builder;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T any(Map<String, Object> map, String key) {
|
||||
Object value = map.get(key);
|
||||
return (T) value;
|
||||
}
|
||||
|
||||
public MapParamBuilder with(String key, Object value) {
|
||||
if (DataUtils.isNotBlank(key) && value != null) {
|
||||
this.params.put(key, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean has(String key) {
|
||||
return this.params.containsKey(key);
|
||||
}
|
||||
|
||||
public MapParamBuilder with(String key, Collection<?> value, Collection<?> defaultValue) {
|
||||
if (DataUtils.isNotBlank(key) && DataUtils.isNotEmpty(value)) {
|
||||
this.params.put(key, value);
|
||||
} else {
|
||||
this.params.put(key, defaultValue);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public MapParamBuilder with(String key, String value) {
|
||||
if (DataUtils.isNotBlank(key) && DataUtils.isNotBlank(value)) {
|
||||
this.params.put(key, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public MapParamBuilder with(String key, Integer value) {
|
||||
// 过滤负值,无意义的值
|
||||
if (DataUtils.isNotBlank(key) && value != null) {
|
||||
this.params.put(key, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 交换键位对应的值
|
||||
*
|
||||
* @param oldKey 要被交换的key
|
||||
* @param newKey 要交换的key
|
||||
* @return 结果
|
||||
*/
|
||||
public MapParamBuilder exchange(String oldKey, String newKey) {
|
||||
if (this.params.containsKey(oldKey) && this.params.containsKey(newKey)) {
|
||||
Object oldValue = this.params.get(oldKey);
|
||||
Object newValue = this.params.get(newKey);
|
||||
this.params.put(oldKey, newValue);
|
||||
this.params.put(newKey, oldValue);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换key为新的key,值不变
|
||||
*
|
||||
* @param oldKey 旧的key
|
||||
* @param newKey 新的key
|
||||
* @return 结果
|
||||
*/
|
||||
public MapParamBuilder replace(String oldKey, String newKey) {
|
||||
Object value = this.params.get(oldKey);
|
||||
if (null != value) {
|
||||
this.params.remove(oldKey);
|
||||
this.params.put(newKey, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public MapParamBuilder clear(String key) {
|
||||
this.params.remove(key);
|
||||
return this;
|
||||
}
|
||||
|
||||
public MapParamBuilder with(String key, Long value) {
|
||||
// 过滤负值,无意义的值
|
||||
if (DataUtils.isNotBlank(key) && value != null) {
|
||||
this.params.put(key, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public MapParamBuilder withAll(Map<String, ?> params) {
|
||||
if (DataUtils.isNotEmpty(params)) {
|
||||
params.forEach(this::with);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public MapParamBuilder withPage(Map<String, Object> params) {
|
||||
if (params.containsKey(PAGE_KEY)) {
|
||||
this.params.put("page", params.get(PAGE_KEY));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T take(String key) {
|
||||
return (T) this.params.get(key);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return DataUtils.isEmpty(params);
|
||||
}
|
||||
|
||||
public Map<String, Object> build() {
|
||||
return this.params;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (DataUtils.isEmpty(this.params)) {
|
||||
return EMPTY_PATTERN;
|
||||
}
|
||||
return JacksonUtil.toJson(this.params).orElse(null);
|
||||
}
|
||||
}
|
@ -0,0 +1,199 @@
|
||||
package group.flyfish.rest.core.builder;
|
||||
|
||||
import group.flyfish.rest.client.RestClient;
|
||||
import group.flyfish.rest.core.entity.Multipart;
|
||||
import group.flyfish.rest.enums.HttpMethod;
|
||||
import group.flyfish.rest.utils.DataUtils;
|
||||
import group.flyfish.rest.utils.JacksonUtil;
|
||||
import group.flyfish.rest.utils.RequestContext;
|
||||
import org.apache.http.client.methods.HttpRequestBase;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static group.flyfish.rest.constants.RestConstants.REQUEST_CONFIG;
|
||||
import static group.flyfish.rest.constants.RestConstants.RESOLVER_MAP;
|
||||
|
||||
/**
|
||||
* 主要的builder,核心构建
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public class RestClientBuilder {
|
||||
|
||||
private String url;
|
||||
|
||||
private HttpMethod method = HttpMethod.GET;
|
||||
|
||||
private Map<String, Object> params;
|
||||
|
||||
private String body;
|
||||
|
||||
private Map<String, String> headers;
|
||||
|
||||
private List<Multipart> multipartList;
|
||||
|
||||
private boolean multipart;
|
||||
|
||||
private boolean credential;
|
||||
|
||||
private String charset;
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public RestClientBuilder url(String url) {
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpMethod getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public RestClientBuilder method(HttpMethod method) {
|
||||
this.method = method;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RestClientBuilder get() {
|
||||
this.method = HttpMethod.GET;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RestClientBuilder post() {
|
||||
this.method = HttpMethod.POST;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RestClientBuilder multipart() {
|
||||
this.multipart = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isMultipart() {
|
||||
return multipart;
|
||||
}
|
||||
|
||||
public Map<String, Object> getParams() {
|
||||
if (null == params) {
|
||||
params = new HashMap<>();
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
public RestClientBuilder queryParams(Map<String, Object> params) {
|
||||
this.params = params;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RestClientBuilder addParam(String key, Object value) {
|
||||
if (null == this.params) {
|
||||
this.params = new HashMap<>();
|
||||
}
|
||||
this.params.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RestClientBuilder charset(String charset) {
|
||||
this.charset = charset;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RestClientBuilder withCredential() {
|
||||
this.credential = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Charset getCharset() {
|
||||
return DataUtils.isBlank(charset) ? Charset.defaultCharset() : Charset.forName(charset);
|
||||
}
|
||||
|
||||
public RestClientBuilder addMultipartBody(String name, String filename, Object data) {
|
||||
if (null == this.multipartList) {
|
||||
this.multipartList = new ArrayList<>();
|
||||
}
|
||||
this.multipartList.add(new Multipart(name, filename, data));
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<Multipart> getMultipartList() {
|
||||
if (null == multipartList) {
|
||||
multipartList = new ArrayList<>();
|
||||
}
|
||||
return multipartList;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public RestClientBuilder body(String body) {
|
||||
this.body = body;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RestClientBuilder body(Object body) {
|
||||
this.body = JacksonUtil.toJson(body).orElse(null);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, String> getHeaders() {
|
||||
if (null == headers) {
|
||||
headers = new HashMap<>();
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
public RestClientBuilder headers(Map<String, String> headers) {
|
||||
this.headers = headers;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RestClientBuilder addHeader(String key, String value) {
|
||||
if (null == this.headers) {
|
||||
this.headers = new HashMap<>();
|
||||
}
|
||||
this.headers.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 匹配解析器
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
private HttpRequestBase buildRequest() {
|
||||
HttpRequestBase request = RESOLVER_MAP.getOrDefault(this.method, RESOLVER_MAP.get(HttpMethod.GET))
|
||||
.resolve(this);
|
||||
// 添加token凭证
|
||||
if (credential) {
|
||||
RequestContext.getCredential().ifPresent(value -> this.addHeader(
|
||||
RequestContext.AUTHORIZATION_KEY, value)
|
||||
);
|
||||
}
|
||||
// 添加头
|
||||
getHeaders().forEach(request::addHeader);
|
||||
// 设置公共设置
|
||||
request.setConfig(REQUEST_CONFIG);
|
||||
// 返回
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建client
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
public RestClient build() {
|
||||
// 创建请求
|
||||
HttpRequestBase request = buildRequest();
|
||||
return new RestClient(request);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package group.flyfish.rest.core.builder;
|
||||
|
||||
import group.flyfish.rest.utils.JacksonUtil;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 有具体泛型类型的Map构建器
|
||||
* 提供基本的非空校验
|
||||
*
|
||||
* @author Mr.Wang
|
||||
*/
|
||||
public final class TypedMapBuilder<K, V> {
|
||||
|
||||
private final Map<K, V> params;
|
||||
|
||||
private TypedMapBuilder() {
|
||||
this.params = new HashMap<>();
|
||||
}
|
||||
|
||||
public static <K, V> TypedMapBuilder<K, V> builder() {
|
||||
return new TypedMapBuilder<>();
|
||||
}
|
||||
|
||||
public static TypedMapBuilder<String, String> stringMapBuilder() {
|
||||
return new TypedMapBuilder<>();
|
||||
}
|
||||
|
||||
public static TypedMapBuilder<String, Object> stringObjectBuilder() {
|
||||
return new TypedMapBuilder<>();
|
||||
}
|
||||
|
||||
public TypedMapBuilder<K, V> with(K key, V value) {
|
||||
if (key != null && value != null) {
|
||||
this.params.put(key, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public TypedMapBuilder<K, V> withAll(Map<K, V> values) {
|
||||
values.forEach(this::with);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<K, V> build() {
|
||||
return params;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return JacksonUtil.toJson(this).orElse(null);
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package group.flyfish.rest.core.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 存储文件上传的part
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
public class Multipart {
|
||||
|
||||
private String name;
|
||||
|
||||
private String filename;
|
||||
|
||||
private Object data;
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package group.flyfish.rest.core.exception;
|
||||
|
||||
/**
|
||||
* 异常类,用于包装异常
|
||||
*/
|
||||
public class RestClientException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 4741281547788724661L;
|
||||
private Exception nested;
|
||||
|
||||
private Object bind;
|
||||
|
||||
public RestClientException(String message, Exception nested) {
|
||||
super(message);
|
||||
this.nested = nested;
|
||||
}
|
||||
|
||||
public RestClientException(String message, Exception nested, Object bind) {
|
||||
super(message);
|
||||
this.nested = nested;
|
||||
this.bind = bind;
|
||||
}
|
||||
|
||||
public RestClientException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getBind() {
|
||||
return (T) bind;
|
||||
}
|
||||
|
||||
public void setBind(Object bind) {
|
||||
this.bind = bind;
|
||||
}
|
||||
|
||||
public Exception getNested() {
|
||||
return nested;
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package group.flyfish.rest.core.produce;
|
||||
|
||||
import group.flyfish.rest.configuration.RestClientProperties;
|
||||
import group.flyfish.rest.utils.DataUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.ssl.SSLContextBuilder;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import java.io.NotActiveException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* 生产httpClient
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Slf4j
|
||||
public final class HttpClientProducer {
|
||||
|
||||
// 使用非公平锁
|
||||
private static final ReentrantLock lock = new ReentrantLock();
|
||||
// 客户端实例,单例
|
||||
private static volatile CloseableHttpClient client;
|
||||
// 配置,配置没进来就不初始化
|
||||
private static RestClientProperties properties;
|
||||
|
||||
/**
|
||||
* 配置信息
|
||||
*
|
||||
* @param properties 属性
|
||||
*/
|
||||
public static void configure(RestClientProperties properties) {
|
||||
HttpClientProducer.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取属性
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
public static RestClientProperties getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生产客户端
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
public static CloseableHttpClient produce() throws NotActiveException {
|
||||
if (properties == null) {
|
||||
log.warn("【rest客户端】未初始化rest客户端配置,生产者未就绪!将使用默认配置!!!");
|
||||
properties = new RestClientProperties();
|
||||
}
|
||||
if (client == null) {
|
||||
// 非公平锁,二次判定,定位volatile
|
||||
lock.lock();
|
||||
try {
|
||||
if (client == null) {
|
||||
client = getClient();
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建单例的httpClient
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
private static CloseableHttpClient getClient() {
|
||||
return DataUtils.isTrue(properties.getAlwaysTrust()) ? createSSLClient() : HttpClients.createDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* 不信任的证书请求客户端
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
private static CloseableHttpClient createSSLClient() {
|
||||
//信任所有
|
||||
try {
|
||||
SSLContext context = SSLContextBuilder.create().loadTrustMaterial(null, (arg0, arg1) -> true).build();
|
||||
SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(context);
|
||||
return HttpClients.custom().setSSLSocketFactory(factory).build();
|
||||
} catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package group.flyfish.rest.core.resolver;
|
||||
|
||||
import group.flyfish.rest.core.builder.RestClientBuilder;
|
||||
import group.flyfish.rest.core.resolver.support.AbstractParamResolver;
|
||||
import org.apache.http.client.methods.HttpDelete;
|
||||
import org.apache.http.client.methods.HttpRequestBase;
|
||||
|
||||
/**
|
||||
* 删除请求的解析器
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public class HttpDeleteResolver extends AbstractParamResolver implements HttpMethodResolver {
|
||||
|
||||
/**
|
||||
* 解析请求
|
||||
*
|
||||
* @param builder 构建器
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public HttpRequestBase resolve(RestClientBuilder builder) {
|
||||
resolveParams(builder);
|
||||
return new HttpDelete(builder.getUrl());
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package group.flyfish.rest.core.resolver;
|
||||
|
||||
import group.flyfish.rest.core.builder.RestClientBuilder;
|
||||
import group.flyfish.rest.core.resolver.support.AbstractParamResolver;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpRequestBase;
|
||||
|
||||
/**
|
||||
* Get方法解析参数的解析器
|
||||
*/
|
||||
public class HttpGetResolver extends AbstractParamResolver implements HttpMethodResolver {
|
||||
|
||||
@Override
|
||||
public HttpRequestBase resolve(RestClientBuilder builder) {
|
||||
resolveParams(builder);
|
||||
return new HttpGet(builder.getUrl());
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package group.flyfish.rest.core.resolver;
|
||||
|
||||
import group.flyfish.rest.core.builder.RestClientBuilder;
|
||||
import org.apache.http.client.methods.HttpRequestBase;
|
||||
|
||||
/**
|
||||
* Http请求解析器
|
||||
*/
|
||||
public interface HttpMethodResolver {
|
||||
|
||||
/**
|
||||
* 解析请求
|
||||
*
|
||||
* @param builder 构建器
|
||||
* @return 结果
|
||||
*/
|
||||
HttpRequestBase resolve(RestClientBuilder builder);
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package group.flyfish.rest.core.resolver;
|
||||
|
||||
import group.flyfish.rest.core.builder.RestClientBuilder;
|
||||
import group.flyfish.rest.core.resolver.support.AbstractBodyResolver;
|
||||
import org.apache.http.client.methods.HttpPatch;
|
||||
import org.apache.http.client.methods.HttpRequestBase;
|
||||
|
||||
/**
|
||||
* patch请求的解析器
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public class HttpPatchResolver extends AbstractBodyResolver implements HttpMethodResolver {
|
||||
|
||||
/**
|
||||
* 解析请求
|
||||
*
|
||||
* @param builder 构建器
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public HttpRequestBase resolve(RestClientBuilder builder) {
|
||||
HttpPatch httpPatch = new HttpPatch(builder.getUrl());
|
||||
httpPatch.setEntity(buildEntity(builder));
|
||||
return httpPatch;
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package group.flyfish.rest.core.resolver;
|
||||
|
||||
import group.flyfish.rest.core.builder.RestClientBuilder;
|
||||
import group.flyfish.rest.core.resolver.support.AbstractBodyResolver;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.methods.HttpRequestBase;
|
||||
|
||||
/**
|
||||
* Post方法解析参数的解析器,包括上传
|
||||
*/
|
||||
public class HttpPostResolver extends AbstractBodyResolver implements HttpMethodResolver {
|
||||
|
||||
@Override
|
||||
public HttpRequestBase resolve(RestClientBuilder builder) {
|
||||
HttpPost post = new HttpPost(builder.getUrl());
|
||||
post.setEntity(buildEntity(builder));
|
||||
return post;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package group.flyfish.rest.core.resolver;
|
||||
|
||||
import group.flyfish.rest.core.builder.RestClientBuilder;
|
||||
import group.flyfish.rest.core.resolver.support.AbstractBodyResolver;
|
||||
import org.apache.http.client.methods.HttpPut;
|
||||
import org.apache.http.client.methods.HttpRequestBase;
|
||||
|
||||
/**
|
||||
* put请求解析器
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public class HttpPutResolver extends AbstractBodyResolver implements HttpMethodResolver {
|
||||
|
||||
/**
|
||||
* 解析请求
|
||||
*
|
||||
* @param builder 构建器
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public HttpRequestBase resolve(RestClientBuilder builder) {
|
||||
HttpPut post = new HttpPut(builder.getUrl());
|
||||
post.setEntity(buildEntity(builder));
|
||||
return post;
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
package group.flyfish.rest.core.resolver.support;
|
||||
|
||||
import group.flyfish.rest.core.builder.RestClientBuilder;
|
||||
import group.flyfish.rest.utils.DataUtils;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.entity.mime.HttpMultipartMode;
|
||||
import org.apache.http.entity.mime.MultipartEntityBuilder;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static group.flyfish.rest.constants.RestConstants.MIME_MAP;
|
||||
|
||||
/**
|
||||
* 抽象的请求体解析
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public abstract class AbstractBodyResolver {
|
||||
|
||||
/**
|
||||
* 构建entity
|
||||
*
|
||||
* @param builder 构建器
|
||||
* @return 结果
|
||||
*/
|
||||
protected HttpEntity buildEntity(RestClientBuilder builder) {
|
||||
return builder.isMultipart() ? buildMultipart(builder)
|
||||
: DataUtils.isNotBlank(builder.getBody()) ? buildJson(builder)
|
||||
: buildFormData(builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建上传数据
|
||||
*
|
||||
* @param clientBuilder builder
|
||||
* @return 结果
|
||||
*/
|
||||
private HttpEntity buildMultipart(RestClientBuilder clientBuilder) {
|
||||
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
|
||||
builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
|
||||
clientBuilder.getMultipartList().forEach(multipart -> {
|
||||
Object data = multipart.getData();
|
||||
String name = multipart.getName();
|
||||
String filename = multipart.getFilename();
|
||||
if (data instanceof byte[]) {
|
||||
builder.addBinaryBody(name, (byte[]) data,
|
||||
ContentType.create(MIME_MAP.getOrDefault(FilenameUtils.getExtension(filename),
|
||||
ContentType.APPLICATION_OCTET_STREAM.getMimeType())), filename);
|
||||
} else if (data instanceof File) {
|
||||
builder.addBinaryBody(name, (File) data,
|
||||
ContentType.create(MIME_MAP.getOrDefault(FilenameUtils.getExtension(filename),
|
||||
ContentType.APPLICATION_OCTET_STREAM.getMimeType())), filename);
|
||||
} else {
|
||||
throw new IllegalArgumentException("上传时,输入的数据不被支持!");
|
||||
}
|
||||
});
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建JSON方式的POST
|
||||
*
|
||||
* @param clientBuilder builder
|
||||
* @return 结果
|
||||
*/
|
||||
private HttpEntity buildJson(RestClientBuilder clientBuilder) {
|
||||
clientBuilder.addHeader("Content-Type", "application/json;charset=UTF-8");
|
||||
Charset charset = clientBuilder.getCharset();
|
||||
StringEntity entity = new StringEntity(clientBuilder.getBody(), charset);
|
||||
entity.setContentEncoding(charset.toString());
|
||||
entity.setContentType("application/json");
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建formdata
|
||||
*
|
||||
* @param clientBuilder builder
|
||||
* @return 结果
|
||||
*/
|
||||
private HttpEntity buildFormData(RestClientBuilder clientBuilder) {
|
||||
// 设置参数
|
||||
Map<String, Object> params = clientBuilder.getParams();
|
||||
List<NameValuePair> list = params.keySet()
|
||||
.stream()
|
||||
.filter(key -> null != params.get(key))
|
||||
.map(key -> new BasicNameValuePair(key, String.valueOf(params.get(key))))
|
||||
.collect(Collectors.toList());
|
||||
if (DataUtils.isNotEmpty(list)) {
|
||||
return new UrlEncodedFormEntity(list, clientBuilder.getCharset());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package group.flyfish.rest.core.resolver.support;
|
||||
|
||||
import group.flyfish.rest.core.builder.RestClientBuilder;
|
||||
import group.flyfish.rest.utils.DataUtils;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 抽象的参数解析逻辑
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public abstract class AbstractParamResolver {
|
||||
|
||||
/**
|
||||
* 解析参数
|
||||
*
|
||||
* @param builder 构建器
|
||||
*/
|
||||
protected void resolveParams(RestClientBuilder builder) {
|
||||
if (DataUtils.isNotEmpty(builder.getParams())) {
|
||||
String start = builder.getUrl().contains("?") ? "&" : "?";
|
||||
String params = builder.getParams().entrySet().stream()
|
||||
.map(entry -> entry.getKey() + "=" + (null == entry.getValue() ? "" : entry.getValue()))
|
||||
.collect(Collectors.joining("&"));
|
||||
builder.url(builder.getUrl() + start + params);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
package group.flyfish.rest.registry;
|
||||
|
||||
import group.flyfish.rest.annotation.EnableRestApiProxy;
|
||||
import group.flyfish.rest.annotation.RestService;
|
||||
import group.flyfish.rest.client.RestClient;
|
||||
import group.flyfish.rest.configuration.RestClientProperties;
|
||||
import group.flyfish.rest.core.produce.HttpClientProducer;
|
||||
import group.flyfish.rest.registry.proxy.RestProxyInvoker;
|
||||
import group.flyfish.rest.registry.proxy.support.RestArgumentResolverComposite;
|
||||
import group.flyfish.rest.utils.DataUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.reflections.Reflections;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.*;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.io.NotActiveException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* rest接口注册机
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class RestApiRegistry implements BeanDefinitionRegistryPostProcessor, BeanFactoryAware {
|
||||
|
||||
private final RestArgumentResolverComposite composite;
|
||||
|
||||
// 包名
|
||||
private final List<String> packageNames = new ArrayList<>();
|
||||
|
||||
// 注册的执行器们,最终会使用配置进行初始化
|
||||
private final List<RestProxyInvoker<?>> invokers = new ArrayList<>();
|
||||
|
||||
// bean工厂
|
||||
private ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
// rest请求参数
|
||||
private RestClientProperties properties;
|
||||
|
||||
/**
|
||||
* 动态注册bean
|
||||
*
|
||||
* @param registry 注册机
|
||||
* @throws BeansException 异常
|
||||
*/
|
||||
@Override
|
||||
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
|
||||
// 找基本包,找不到立马报错
|
||||
beanFactory.getBeansWithAnnotation(EnableRestApiProxy.class)
|
||||
.forEach((key, value) -> {
|
||||
EnableRestApiProxy proxy = value.getClass().getAnnotation(EnableRestApiProxy.class);
|
||||
for (String basePackage : proxy.basePackages()) {
|
||||
if (DataUtils.isNotBlank(basePackage)) {
|
||||
packageNames.add(basePackage);
|
||||
}
|
||||
}
|
||||
});
|
||||
// 不为空时查找
|
||||
if (DataUtils.isNotEmpty(packageNames)) {
|
||||
// 初始化反射
|
||||
try {
|
||||
Reflections reflections = new Reflections(packageNames.toArray());
|
||||
// 得到Resource注解的类
|
||||
Set<Class<?>> classSet = reflections.getTypesAnnotatedWith(RestService.class);
|
||||
// 代理并生成子类,并注册到ioc容器
|
||||
classSet.forEach(clazz -> registry.registerBeanDefinition(clazz.getName(),
|
||||
BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> DataUtils.cast(RestProxyInvoker
|
||||
.produce(clazz, composite, this::bindInvoker)))
|
||||
.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE).getRawBeanDefinition())
|
||||
);
|
||||
} catch (IllegalStateException e) {
|
||||
log.error("初始化Rest映射时出错", e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
throw new BeanDefinitionValidationException("【RestApi】EnableRestApiProxy注解必须指定有效的basePackage!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化bean们
|
||||
*
|
||||
* @param properties 属性
|
||||
* @throws NotActiveException 未活动异常
|
||||
*/
|
||||
public void initialize(RestClientProperties properties) throws NotActiveException {
|
||||
log.info("bean加载完成");
|
||||
// 真正加载完成的回调
|
||||
this.properties = properties;
|
||||
// 配置参数
|
||||
HttpClientProducer.configure(properties);
|
||||
// 最后再配置,生成
|
||||
RestClient.setClient(HttpClientProducer.produce());
|
||||
// 初始化
|
||||
this.invokers.forEach(invoker -> invoker.configure(properties));
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定执行器
|
||||
*
|
||||
* @param invoker 执行器
|
||||
* @param <T> 泛型
|
||||
*/
|
||||
private <T> void bindInvoker(RestProxyInvoker<T> invoker) {
|
||||
if (properties == null) {
|
||||
this.invokers.add(invoker);
|
||||
} else {
|
||||
invoker.configure(properties);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public void setBeanFactory(ConfigurableListableBeanFactory beanFactory) {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
Assert.isTrue(beanFactory instanceof ConfigurableListableBeanFactory, "当前bean factory不被支持!");
|
||||
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package group.flyfish.rest.registry;
|
||||
|
||||
/**
|
||||
* 用于标记rest服务代理
|
||||
*
|
||||
* @author wangyu
|
||||
* @deprecated 该类已经过时,请使用新版的注解声明
|
||||
* @see group.flyfish.rest.annotation.RestService
|
||||
*/
|
||||
@Deprecated
|
||||
public interface RestService {
|
||||
}
|
@ -0,0 +1,232 @@
|
||||
package group.flyfish.rest.registry.proxy;
|
||||
|
||||
import group.flyfish.rest.annotation.AutoMapping;
|
||||
import group.flyfish.rest.annotation.RestApi;
|
||||
import group.flyfish.rest.annotation.RestService;
|
||||
import group.flyfish.rest.client.RestClient;
|
||||
import group.flyfish.rest.configuration.RestClientProperties;
|
||||
import group.flyfish.rest.core.builder.RestClientBuilder;
|
||||
import group.flyfish.rest.mapping.RestResultMapping;
|
||||
import group.flyfish.rest.registry.proxy.support.ArgumentResolveContext;
|
||||
import group.flyfish.rest.registry.proxy.support.RestArgumentResolverComposite;
|
||||
import group.flyfish.rest.registry.proxy.support.UrlCompiler;
|
||||
import group.flyfish.rest.registry.wrapper.DefaultRestResultMapping;
|
||||
import group.flyfish.rest.utils.DataUtils;
|
||||
import group.flyfish.rest.utils.JacksonUtil;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import java.beans.Transient;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Rest代理执行器
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public class RestProxyInvoker<T> implements InvocationHandler {
|
||||
|
||||
// 要代理的目标类
|
||||
private final Class<T> targetType;
|
||||
// 解析集合
|
||||
private final RestArgumentResolverComposite composite;
|
||||
// 初始的基本路径
|
||||
private String baseUrl;
|
||||
// 基础url
|
||||
private RestClientProperties properties;
|
||||
// 结果映射
|
||||
private RestResultMapping mapping;
|
||||
|
||||
/**
|
||||
* 构造器
|
||||
*
|
||||
* @param targetType 目标类型
|
||||
* @param composite 解析器
|
||||
*/
|
||||
public RestProxyInvoker(Class<T> targetType, RestArgumentResolverComposite composite) {
|
||||
this.targetType = targetType;
|
||||
this.composite = composite;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生产一个实现类
|
||||
*
|
||||
* @param target 目标
|
||||
* @param <T> 泛型
|
||||
* @return 结果
|
||||
*/
|
||||
public static <T> T produce(Class<T> target, RestArgumentResolverComposite composite, Consumer<RestProxyInvoker<T>> consumer) {
|
||||
RestProxyInvoker<T> invoker = new RestProxyInvoker<>(target, composite);
|
||||
consumer.accept(invoker);
|
||||
return DataUtils.cast(Proxy.newProxyInstance(target.getClassLoader(), new Class[]{target}, invoker));
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用配置信息对接口进行配置
|
||||
*
|
||||
* @param properties 配置
|
||||
*/
|
||||
public void configure(RestClientProperties properties) {
|
||||
this.properties = properties;
|
||||
this.baseUrl = this.findBaseUrl();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 执行rest请求的地方,这里很简单易懂
|
||||
*
|
||||
* @param proxy 代理对象
|
||||
* @param method 代理方法
|
||||
* @param args 参数
|
||||
* @return 结果
|
||||
* @throws Throwable 可能抛出的异常
|
||||
*/
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
// 处理基本方法被代理的情况
|
||||
if (AopUtils.isEqualsMethod(method)) {
|
||||
Object obj = args[0];
|
||||
// The target does not implement the equals(Object) method itself.
|
||||
return null != obj && ClassUtils.isAssignable(targetType, obj.getClass());
|
||||
} else if (AopUtils.isHashCodeMethod(method)) {
|
||||
// The target does not implement the hashCode() method itself.
|
||||
return -1;
|
||||
}
|
||||
// 无视proxy,因为啥也没
|
||||
RestApi restApi = AnnotationUtils.findAnnotation(method, RestApi.class);
|
||||
if (null == restApi) {
|
||||
throw new IllegalAccessException("【Rest调用】未声明rest配置的方法被调用!请检查代码!");
|
||||
}
|
||||
// 第一步就解析参数
|
||||
ArgumentResolveContext context = composite.resolve(restApi, method, args);
|
||||
// 构造和调用,这里的restClient不保存状态
|
||||
RestClientBuilder builder = RestClient.create()
|
||||
.url(determineUrl(restApi, context))
|
||||
.method(restApi.method());
|
||||
// 需要带cookie的带上
|
||||
if (restApi.credentials()) {
|
||||
builder.withCredential();
|
||||
}
|
||||
// 判断情况,赋值参数
|
||||
if (context.hasBody()) {
|
||||
builder.body(context.getBody());
|
||||
}
|
||||
// 赋值参数们
|
||||
if (context.hasParams()) {
|
||||
builder.queryParams(context.getParam());
|
||||
}
|
||||
// 赋值头
|
||||
if (context.hasHeaders()) {
|
||||
builder.headers(context.getHeaders());
|
||||
}
|
||||
// 赋值头部
|
||||
RestClient client = builder.build();
|
||||
// 是否对结果进行映射
|
||||
boolean map = null != mapping && null == AnnotationUtils.findAnnotation(method, Transient.class);
|
||||
// 执行请求
|
||||
Object result = execute(client, method, map);
|
||||
// 结果映射
|
||||
return map ? mapping.map(result) : result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 最终执行的方法
|
||||
*
|
||||
* @param client rest客户端实例
|
||||
* @param method 原方法实例
|
||||
* @param map 是否映射结果
|
||||
* @return 结果
|
||||
*/
|
||||
private Object execute(RestClient client, Method method, boolean map) throws IOException {
|
||||
// 构建带泛型的返回值,自动判断是否是简单类型
|
||||
JavaType constructed = JacksonUtil.getMapper().constructType(method.getGenericReturnType());
|
||||
// 特殊处理映射
|
||||
if (map) {
|
||||
Type resolved = mapping.resolve(constructed);
|
||||
// 返回java泛型
|
||||
if (resolved instanceof JavaType) {
|
||||
return client.execute((JavaType) resolved);
|
||||
} else if (resolved instanceof Class) {
|
||||
// 简单类型
|
||||
return client.execute((Class<?>) resolved);
|
||||
}
|
||||
}
|
||||
// 优先构建,对于map来说,只支持模糊map,否则可能会报错,直接返回
|
||||
if (ClassUtils.isAssignable(Map.class, method.getReturnType())) {
|
||||
return client.executeForMap();
|
||||
}
|
||||
// 不是map,直接返回构建结果类型
|
||||
return client.execute(constructed);
|
||||
}
|
||||
|
||||
/**
|
||||
* 找到配置固化的基本url
|
||||
*/
|
||||
private String findBaseUrl() {
|
||||
// 注解的优先级高于全局基本路径
|
||||
RestService restService = AnnotationUtils.findAnnotation(targetType, RestService.class);
|
||||
// 当且仅当存在时进入
|
||||
if (null != restService) {
|
||||
// 注解的路径解析
|
||||
String key = restService.value();
|
||||
String baseUrl = restService.baseUrl();
|
||||
// 更友好的处理解包
|
||||
AutoMapping autoMapping = AnnotationUtils.findAnnotation(targetType, AutoMapping.class);
|
||||
if (null != autoMapping) {
|
||||
// 找到返回值处理器
|
||||
Class<?> clazz = autoMapping.value();
|
||||
// 处理返回值
|
||||
if (Object.class.equals(clazz)) {
|
||||
this.mapping = DefaultRestResultMapping.getInstance();
|
||||
} else {
|
||||
this.mapping = RestResultMapping.MAPPINGS.get(clazz);
|
||||
}
|
||||
}
|
||||
// 当key不为空,解析字典路径
|
||||
if (DataUtils.isNotBlank(key)) {
|
||||
return properties.getDictUrl(key);
|
||||
} else if (DataUtils.isNotBlank(baseUrl)) {
|
||||
return baseUrl;
|
||||
}
|
||||
}
|
||||
// 解包生效,以标准模式解包
|
||||
return properties.getBaseUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* 决定基本url,优先级: 方法注解url > 方法注解baseUrl + uri > 全局配置 + uri
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
private String determineUrl(RestApi restApi, ArgumentResolveContext context) {
|
||||
String url;
|
||||
// 解析url以支持PathVariable
|
||||
if (DataUtils.isNotBlank(restApi.url())) {
|
||||
url = restApi.url();
|
||||
} else {
|
||||
// 构建基础url,优先级从小到大依次找。同时尝试取字典值
|
||||
Optional<String> baseUrl = Stream.of(restApi.baseUrl(), this.baseUrl)
|
||||
.filter(DataUtils::isNotBlank)
|
||||
.findFirst()
|
||||
.map(found -> found.startsWith("#") ? properties.getDictUrl(found.substring(1)) : found);
|
||||
// 判定和赋值
|
||||
if (baseUrl.isPresent() && DataUtils.isNotBlank(restApi.uri())) {
|
||||
url = baseUrl.get() + restApi.uri();
|
||||
} else {
|
||||
throw new IllegalArgumentException("【Rest调用】未指定url或baseurl,无法调用远端服务器!");
|
||||
}
|
||||
}
|
||||
// 尝试解析路径参数
|
||||
return context.hasPathParams() ? UrlCompiler.compile(url, context.getPathParams()) : url;
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
package group.flyfish.rest.registry.proxy.support;
|
||||
|
||||
import group.flyfish.rest.annotation.RestApi;
|
||||
import group.flyfish.rest.utils.DataUtils;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 参数解析上下文
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class ArgumentResolveContext {
|
||||
|
||||
// 注解
|
||||
private RestApi annotation;
|
||||
|
||||
// 参数
|
||||
private Map<String, Object> param;
|
||||
|
||||
// 路径参数
|
||||
private Map<String, Object> pathParams;
|
||||
|
||||
// 请求头
|
||||
private Map<String, String> headers;
|
||||
|
||||
// 请求体
|
||||
private Object body;
|
||||
|
||||
// 设置参数
|
||||
public void setParam(String key, Object value) {
|
||||
if (DataUtils.isEmpty(param)) {
|
||||
param = new HashMap<>();
|
||||
}
|
||||
param.put(key, value);
|
||||
}
|
||||
|
||||
// 设置请求体(分拣模式)
|
||||
public void setBody(String key, Object value) {
|
||||
if (null == body || !(body instanceof Map)) {
|
||||
body = new HashMap<>();
|
||||
}
|
||||
Map<String, Object> map = DataUtils.cast(body);
|
||||
map.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置头部
|
||||
*
|
||||
* @param headers 头
|
||||
*/
|
||||
public void setHeaders(Map<String, String> headers) {
|
||||
if (DataUtils.isEmpty(this.headers)) {
|
||||
this.headers = new HashMap<>();
|
||||
}
|
||||
this.headers.putAll(headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置单个头
|
||||
*
|
||||
* @param name 名称
|
||||
* @param value 值
|
||||
*/
|
||||
public void setHeader(String name, String value) {
|
||||
if (DataUtils.isEmpty(this.headers)) {
|
||||
this.headers = new HashMap<>();
|
||||
}
|
||||
this.headers.put(name, value);
|
||||
}
|
||||
|
||||
// 设置路径参数
|
||||
public void setPathParam(String key, Object value) {
|
||||
if (DataUtils.isEmpty(pathParams)) {
|
||||
pathParams = new HashMap<>();
|
||||
}
|
||||
pathParams.put(key, value);
|
||||
}
|
||||
|
||||
public boolean hasPathParams() {
|
||||
return DataUtils.isNotEmpty(pathParams);
|
||||
}
|
||||
|
||||
public boolean hasBody() {
|
||||
return null != body;
|
||||
}
|
||||
|
||||
public boolean hasHeaders() {
|
||||
return DataUtils.isNotEmpty(headers);
|
||||
}
|
||||
|
||||
public boolean hasParams() {
|
||||
return DataUtils.isNotEmpty(param);
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package group.flyfish.rest.registry.proxy.support;
|
||||
|
||||
import java.lang.reflect.Parameter;
|
||||
|
||||
/**
|
||||
* 参数解析器
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public interface RestArgumentResolver {
|
||||
|
||||
/**
|
||||
* 是否支持
|
||||
*
|
||||
* @param parameter 参数
|
||||
* @return 结果
|
||||
*/
|
||||
boolean support(Parameter parameter);
|
||||
|
||||
/**
|
||||
* 解析
|
||||
*
|
||||
* @param context 上下文,赋值
|
||||
* @param parameter 参数
|
||||
* @param value 值
|
||||
*/
|
||||
void resolve(ArgumentResolveContext context, Parameter parameter, Object value);
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package group.flyfish.rest.registry.proxy.support;
|
||||
|
||||
import group.flyfish.rest.annotation.RestApi;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* 解析器集合
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public class RestArgumentResolverComposite {
|
||||
|
||||
// 解析器们
|
||||
private final List<RestArgumentResolver> resolvers;
|
||||
|
||||
public RestArgumentResolverComposite(List<RestArgumentResolver> resolvers) {
|
||||
this.resolvers = resolvers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行解析
|
||||
*
|
||||
* @param annotation 注解
|
||||
* @param method 方法
|
||||
* @param args 参数
|
||||
* @return 结果
|
||||
*/
|
||||
public ArgumentResolveContext resolve(RestApi annotation, Method method, Object[] args) {
|
||||
// 上下文
|
||||
ArgumentResolveContext context = ArgumentResolveContext.builder().annotation(annotation).build();
|
||||
// 参数元
|
||||
Parameter[] parameters = method.getParameters();
|
||||
// 循环处理
|
||||
IntStream.range(0, parameters.length)
|
||||
.forEach(index -> resolveInternal(context, parameters[index], args[index]));
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部解析
|
||||
*
|
||||
* @param context 上下文
|
||||
* @param parameter 参数
|
||||
* @param value 值
|
||||
*/
|
||||
private void resolveInternal(ArgumentResolveContext context, Parameter parameter, Object value) {
|
||||
// 开始解析
|
||||
resolvers.stream().filter(resolver -> resolver.support(parameter)).findFirst()
|
||||
.ifPresent(resolver -> resolver.resolve(context, parameter, value));
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package group.flyfish.rest.registry.proxy.support;
|
||||
|
||||
import group.flyfish.rest.core.builder.MapParamBuilder;
|
||||
import group.flyfish.rest.utils.DataUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* url编译器
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public class UrlCompiler {
|
||||
|
||||
// 匹配正则预编译
|
||||
private static final Pattern PATTERN = Pattern.compile("\\{\\w+}");
|
||||
|
||||
/**
|
||||
* 地址原路径编译
|
||||
*
|
||||
* @param source 源
|
||||
* @return 结果
|
||||
*/
|
||||
public static String compile(String source, Map<String, Object> params) {
|
||||
Matcher matcher = PATTERN.matcher(source);
|
||||
// 开始查找和替换
|
||||
while (matcher.find()) {
|
||||
String found = matcher.group();
|
||||
String key = DataUtils.substringBetween(found, "{", "}");
|
||||
if (params.containsKey(key)) {
|
||||
source = source.replace(found, String.valueOf(params.get(key)));
|
||||
}
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试
|
||||
*
|
||||
* @param args args参数
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
String result = UrlCompiler.compile("http://www.baidu.com/love/{target}/{mobile}/{shit}", MapParamBuilder.builder()
|
||||
.with("target", "nanami")
|
||||
.with("mobile", 1223123)
|
||||
.with("shit", new HashMap<>())
|
||||
.build());
|
||||
System.out.println(result);
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package group.flyfish.rest.registry.proxy.support.resolvers;
|
||||
|
||||
import group.flyfish.rest.annotation.RestBody;
|
||||
import group.flyfish.rest.registry.proxy.support.ArgumentResolveContext;
|
||||
import group.flyfish.rest.registry.proxy.support.RestArgumentResolver;
|
||||
|
||||
import java.lang.reflect.Parameter;
|
||||
|
||||
/**
|
||||
* 请求体参数解析
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public class RestBodyArgumentResolver implements RestArgumentResolver {
|
||||
|
||||
/**
|
||||
* 是否支持
|
||||
*
|
||||
* @param parameter 参数
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public boolean support(Parameter parameter) {
|
||||
return null != parameter.getAnnotation(RestBody.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析
|
||||
*
|
||||
* @param context 上下文,赋值
|
||||
* @param parameter 参数
|
||||
* @param value 值
|
||||
*/
|
||||
@Override
|
||||
public void resolve(ArgumentResolveContext context, Parameter parameter, Object value) {
|
||||
// 无视合并body,这里的优先级最高
|
||||
context.setBody(value);
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package group.flyfish.rest.registry.proxy.support.resolvers;
|
||||
|
||||
import group.flyfish.rest.annotation.RestHeader;
|
||||
import group.flyfish.rest.registry.proxy.support.ArgumentResolveContext;
|
||||
import group.flyfish.rest.registry.proxy.support.RestArgumentResolver;
|
||||
import group.flyfish.rest.utils.DataUtils;
|
||||
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 请求头解析策略
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public class RestHeaderArgumentResolver implements RestArgumentResolver {
|
||||
|
||||
/**
|
||||
* 是否支持
|
||||
*
|
||||
* @param parameter 参数
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public boolean support(Parameter parameter) {
|
||||
return null != parameter.getAnnotation(RestHeader.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析
|
||||
*
|
||||
* @param context 上下文,赋值
|
||||
* @param parameter 参数
|
||||
* @param value 值
|
||||
*/
|
||||
@Override
|
||||
public void resolve(ArgumentResolveContext context, Parameter parameter, Object value) {
|
||||
if (value instanceof Map) {
|
||||
((Map<?, ?>) value).forEach((k, v) -> context.setHeader((String) k, String.valueOf(v)));
|
||||
} else {
|
||||
RestHeader header = parameter.getAnnotation(RestHeader.class);
|
||||
String name = DataUtils.isNotBlank(header.value()) ? header.value() : parameter.getName();
|
||||
context.setHeader(name, null != value ? String.valueOf(value) : "");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
package group.flyfish.rest.registry.proxy.support.resolvers;
|
||||
|
||||
import group.flyfish.rest.annotation.RestParam;
|
||||
import group.flyfish.rest.annotation.RestParams;
|
||||
import group.flyfish.rest.registry.proxy.support.ArgumentResolveContext;
|
||||
import group.flyfish.rest.registry.proxy.support.RestArgumentResolver;
|
||||
import group.flyfish.rest.utils.DataUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 参数解析
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Slf4j
|
||||
public class RestParamArgumentResolver implements RestArgumentResolver {
|
||||
|
||||
/**
|
||||
* 是否支持
|
||||
*
|
||||
* @param parameter 参数
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public boolean support(Parameter parameter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析
|
||||
*
|
||||
* @param context 上下文,赋值
|
||||
* @param parameter 参数
|
||||
* @param value 值
|
||||
*/
|
||||
@Override
|
||||
public void resolve(ArgumentResolveContext context, Parameter parameter, Object value) {
|
||||
// 当参数包含@RestParams注解,使用BeanDescriptor处理
|
||||
if (null != parameter.getAnnotation(RestParams.class) || ClassUtils.isAssignable(Map.class, parameter.getType())) {
|
||||
resolveParams(context, parameter, value);
|
||||
} else {
|
||||
// 取得合法的名称
|
||||
String name = Optional.ofNullable(parameter.getAnnotation(RestParam.class))
|
||||
.map(RestParam::value)
|
||||
.filter(DataUtils::isNotBlank)
|
||||
.orElse(parameter.getName());
|
||||
// 启用合并请求体,合并入
|
||||
if (context.getAnnotation().mergedBody()) {
|
||||
context.setBody(name, value);
|
||||
} else {
|
||||
context.setParam(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析多个参数
|
||||
*
|
||||
* @param parameter 参数
|
||||
* @param value 值
|
||||
*/
|
||||
private void resolveParams(ArgumentResolveContext context, Parameter parameter, Object value) {
|
||||
// 参数注解存在,报出错误
|
||||
if (null != parameter.getAnnotation(RestParam.class)) {
|
||||
throw new IllegalArgumentException("无法将对象作为一个普通的参数!");
|
||||
}
|
||||
// 非空才处理
|
||||
if (null != value) {
|
||||
// 是map,直接解包赋值
|
||||
if (ClassUtils.isAssignable(Map.class, parameter.getType())) {
|
||||
Map<String, Object> values = DataUtils.cast(value);
|
||||
values.forEach((k, v) -> {
|
||||
if (null != v) {
|
||||
context.setParam(k, String.valueOf(v));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 对象,解析后混入
|
||||
for (PropertyDescriptor propertyDescriptor : BeanUtils.getPropertyDescriptors(value.getClass())) {
|
||||
if ("class".equalsIgnoreCase(propertyDescriptor.getName())) {
|
||||
continue;
|
||||
}
|
||||
Object v = null;
|
||||
try {
|
||||
v = propertyDescriptor.getReadMethod().invoke(value);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
log.error("【Rest客户端】尝试解析参数时发生异常!获取bean的属性表失败!{}", e.getMessage(), e);
|
||||
}
|
||||
if (null != v) {
|
||||
context.setParam(propertyDescriptor.getName(), String.valueOf(v));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package group.flyfish.rest.registry.proxy.support.resolvers;
|
||||
|
||||
import group.flyfish.rest.annotation.RestPathParam;
|
||||
import group.flyfish.rest.registry.proxy.support.ArgumentResolveContext;
|
||||
import group.flyfish.rest.registry.proxy.support.RestArgumentResolver;
|
||||
import group.flyfish.rest.utils.DataUtils;
|
||||
|
||||
import java.lang.reflect.Parameter;
|
||||
|
||||
/**
|
||||
* 解析路径参数
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public class RestPathParamArgumentResolver implements RestArgumentResolver {
|
||||
|
||||
/**
|
||||
* 是否支持
|
||||
*
|
||||
* @param parameter 参数
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public boolean support(Parameter parameter) {
|
||||
return null != parameter.getAnnotation(RestPathParam.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析
|
||||
*
|
||||
* @param context 上下文,赋值
|
||||
* @param parameter 参数
|
||||
* @param value 值
|
||||
*/
|
||||
@Override
|
||||
public void resolve(ArgumentResolveContext context, Parameter parameter, Object value) {
|
||||
RestPathParam annotation = parameter.getAnnotation(RestPathParam.class);
|
||||
String name = DataUtils.isNotBlank(annotation.value()) ? annotation.value() : parameter.getName();
|
||||
context.setPathParam(parameter.getName(), value);
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package group.flyfish.rest.registry.wrapper;
|
||||
|
||||
import group.flyfish.rest.core.exception.RestClientException;
|
||||
import group.flyfish.rest.mapping.RestResultMapping;
|
||||
import group.flyfish.rest.utils.TypeResolveUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.client.utils.DateUtils;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 默认缺省的结果映射
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Slf4j
|
||||
public class DefaultRestResultMapping implements RestResultMapping {
|
||||
|
||||
/**
|
||||
* 获取内部类单例
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
public static DefaultRestResultMapping getInstance() {
|
||||
return SingletonHolder.INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 模糊的结果映射
|
||||
*
|
||||
* @param body 结果
|
||||
* @return 映射后的结果
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T map(Object body) throws RestClientException {
|
||||
if (body instanceof RestResult) {
|
||||
RestResult<?> result = (RestResult<?>) body;
|
||||
if (result.isSuccess()) {
|
||||
return (T) result.getResult();
|
||||
}
|
||||
log.error("【RestProxy】请求发生异常!状态码:{},时间:{},信息:{}", result.getCode(),
|
||||
DateUtils.formatDate(new Date(result.getTimestamp()), "yyyy-MM-dd HH:mm:ss"), result.getMessage());
|
||||
throw new RestClientException(result.getMessage());
|
||||
}
|
||||
return (T) body;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析返回类型
|
||||
*
|
||||
* @param resultType 返回类型
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Type resolve(Type resultType) {
|
||||
return TypeResolveUtils.wrap(resultType, RestResult.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态初始化器,由JVM来保证线程安全
|
||||
*/
|
||||
private interface SingletonHolder {
|
||||
DefaultRestResultMapping INSTANCE = new DefaultRestResultMapping();
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package group.flyfish.rest.registry.wrapper;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 标准的解包结构,等同于全局
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Data
|
||||
public class RestResult<T> {
|
||||
|
||||
/**
|
||||
* 成功标志
|
||||
*/
|
||||
private boolean success = true;
|
||||
|
||||
/**
|
||||
* 返回处理消息
|
||||
*/
|
||||
private String message = "操作成功!";
|
||||
|
||||
/**
|
||||
* 返回代码
|
||||
*/
|
||||
private Integer code = 0;
|
||||
|
||||
/**
|
||||
* 返回数据对象 data
|
||||
*/
|
||||
private T result;
|
||||
|
||||
/**
|
||||
* 时间戳
|
||||
*/
|
||||
private long timestamp = System.currentTimeMillis();
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
package group.flyfish.rest.utils;
|
||||
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 数据工具类,仅限于rest模块使用
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public final class DataUtils {
|
||||
|
||||
public static final int INDEX_NOT_FOUND = -1;
|
||||
|
||||
/**
|
||||
* 判断字符串是否不为空
|
||||
*
|
||||
* @param target 目标字符串
|
||||
* @return 结果
|
||||
*/
|
||||
public static boolean isNotBlank(String target) {
|
||||
return !StringUtils.isEmpty(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制转换和平滑过渡
|
||||
*
|
||||
* @param source 原对象
|
||||
* @param <T> 原类型
|
||||
* @param <R> 目标类型
|
||||
* @return 结果
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T, R> T cast(R source) {
|
||||
return (T) source;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断集合是否不为空
|
||||
*
|
||||
* @param collection 集合
|
||||
* @return 结果
|
||||
*/
|
||||
public static boolean isNotEmpty(Collection<?> collection) {
|
||||
return !CollectionUtils.isEmpty(collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断map是否不为空
|
||||
*
|
||||
* @param map 一个字典
|
||||
* @return 结果
|
||||
*/
|
||||
public static boolean isNotEmpty(Map<?, ?> map) {
|
||||
return !CollectionUtils.isEmpty(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断map是否为囧
|
||||
*
|
||||
* @param map 一个字典
|
||||
* @return 结果
|
||||
*/
|
||||
public static boolean isEmpty(Map<?, ?> map) {
|
||||
return CollectionUtils.isEmpty(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为空
|
||||
*
|
||||
* @param target 目标
|
||||
* @return 结果
|
||||
*/
|
||||
public static boolean isBlank(String target) {
|
||||
return StringUtils.isEmpty(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找两个字符串之间的内容
|
||||
*
|
||||
* @param str 字符串
|
||||
* @param open 开始
|
||||
* @param close 结尾
|
||||
* @return 结果
|
||||
*/
|
||||
public static String substringBetween(final String str, final String open, final String close) {
|
||||
if (str == null || open == null || close == null) {
|
||||
return null;
|
||||
}
|
||||
final int start = str.indexOf(open);
|
||||
if (start != INDEX_NOT_FOUND) {
|
||||
final int end = str.indexOf(close, start + open.length());
|
||||
if (end != INDEX_NOT_FOUND) {
|
||||
return str.substring(start + open.length(), end);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是不是true
|
||||
*
|
||||
* @param value 值
|
||||
* @return 结果
|
||||
*/
|
||||
public static boolean isTrue(Boolean value) {
|
||||
return Boolean.TRUE.equals(value);
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package group.flyfish.rest.utils;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class IocBeans implements ApplicationContextAware {
|
||||
|
||||
private static IocBeans instance;
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
public IocBeans() {
|
||||
instance = this;
|
||||
}
|
||||
|
||||
public static IocBeans sharedInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static IocBeans sharedInstance(ApplicationContext applicationContext) {
|
||||
if (null != instance && null == instance.applicationContext) {
|
||||
instance.applicationContext = applicationContext;
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 注册bean
|
||||
*
|
||||
* @param name bean的名称
|
||||
* @param beanClass bean的类
|
||||
* @param beanSupplier bean实例化
|
||||
*/
|
||||
public static <T> T registerBean(String name, Class<T> beanClass, Supplier<T> beanSupplier) {
|
||||
return sharedInstance().register(name, beanClass, beanSupplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册bean
|
||||
*
|
||||
* @param name bean的名称
|
||||
* @param beanClass bean的类
|
||||
* @param beanSupplier bean实例化
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T register(String name, Class<T> beanClass, Supplier<T> beanSupplier) {
|
||||
// 已经包含bean
|
||||
if (applicationContext.containsBean(name)) {
|
||||
Object wired = applicationContext.getBean(name);
|
||||
if (wired.getClass().isAssignableFrom(beanClass)) {
|
||||
return (T) wired;
|
||||
} else {
|
||||
throw new RuntimeException("注册RMI实例出错,Bean名称重复:" + name);
|
||||
}
|
||||
}
|
||||
// 构建bean定义
|
||||
BeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(beanClass, beanSupplier)
|
||||
.getRawBeanDefinition();
|
||||
// 写入
|
||||
return Optional.ofNullable(getBeanFactory())
|
||||
.map(factory -> {
|
||||
factory.registerBeanDefinition(name, beanDefinition);
|
||||
return applicationContext.getBean(name, beanClass);
|
||||
}).orElseThrow(() -> new RuntimeException("注册RMI实例出错,BeanFactory不存在!"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取beanFactory
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
@Nullable
|
||||
public DefaultListableBeanFactory getBeanFactory() {
|
||||
BeanFactory beanFactory = applicationContext.getAutowireCapableBeanFactory();
|
||||
if (beanFactory == null) {
|
||||
beanFactory = applicationContext.getParentBeanFactory();
|
||||
}
|
||||
return (DefaultListableBeanFactory) beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
package group.flyfish.rest.utils;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Jackson序列化工具类
|
||||
*
|
||||
* @author Mr.Wang
|
||||
*/
|
||||
public final class JacksonUtil {
|
||||
|
||||
private static final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
static {
|
||||
// =========================================================================
|
||||
// SerializationFeature for changing how JSON is written
|
||||
|
||||
// to enable standard indentation ("pretty-printing"):
|
||||
// mapper.enable(SerializationFeature.INDENT_OUTPUT);
|
||||
// to allow serialization of "empty" POJOs (no properties to serialize)
|
||||
// (without this setting, an exception is thrown in those cases)
|
||||
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
|
||||
// to write java.util.Date, Calendar as number (timestamp):
|
||||
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
|
||||
// DeserializationFeature for changing how JSON is read as POJOs:
|
||||
|
||||
// to prevent exception when encountering unknown property:
|
||||
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
// to allow coercion of JSON empty String ("") to null Object value:
|
||||
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
|
||||
|
||||
// =========================================================================
|
||||
// JsonParser.Feature for configuring parsing settings:
|
||||
|
||||
// to allow C/C++ style comments in JSON (non-standard, disabled by default)
|
||||
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
|
||||
// (note: with Jackson 2.5, there is also `mapper.enable(feature)` / `mapper.disable(feature)`)
|
||||
mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
|
||||
// to allow (non-standard) unquoted field names in JSON:
|
||||
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
|
||||
// to allow use of apostrophes (single quotes), non standard
|
||||
mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
|
||||
|
||||
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
// JsonGenerator.Feature for configuring low-level JSON generation:
|
||||
|
||||
// to force escaping of non-ASCII characters:
|
||||
mapper.configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true);
|
||||
}
|
||||
|
||||
public static <T> T fromJson(final String json) {
|
||||
return readValue(json, new TypeReference<T>() {
|
||||
});
|
||||
}
|
||||
|
||||
public static <T> T fromJson(final String json, TypeReference<T> reference) {
|
||||
return readValue(json, reference);
|
||||
}
|
||||
|
||||
public static <T> T fromJson(final String json, Class<T> clazz) {
|
||||
if (null == json || "".equals(json)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return mapper.readValue(json, clazz);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T> T fromJson(final String json, JavaType type) {
|
||||
if (null == json || "".equals(json)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return mapper.readValue(json, type);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// public static Map json2Map(final String json) {
|
||||
// return fromJson(json, Map.class);
|
||||
// }
|
||||
|
||||
public static <K, V> Map<K, V> json2Map(String json) {
|
||||
return readValue(json, new TypeReference<Map<K, V>>() {
|
||||
});
|
||||
}
|
||||
|
||||
public static <T> List<T> json2List(final String json) {
|
||||
return readValue(json, new TypeReference<List<T>>() {
|
||||
});
|
||||
}
|
||||
|
||||
public static <T> List<T> json2List(final String json, Class<T> clazz) {
|
||||
if (null == json || "".equals(json)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
try {
|
||||
return mapper.readValue(json, mapper.getTypeFactory().constructParametricType(List.class, clazz));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public static Optional<String> toJson(final Object obj) {
|
||||
try {
|
||||
return Optional.of(mapper.writeValueAsString(obj));
|
||||
} catch (JsonProcessingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static <T> T readValue(String json, TypeReference<T> valueTypeRef) {
|
||||
if (null == json || "".equals(json)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return mapper.readValue(json, valueTypeRef);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static ObjectMapper getMapper() {
|
||||
return mapper;
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package group.flyfish.rest.utils;
|
||||
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 请求上下文
|
||||
*
|
||||
* @author wangyu
|
||||
* 基于spring安全调用
|
||||
*/
|
||||
public final class RequestContext {
|
||||
|
||||
public static final String AUTHORIZATION_KEY = "Authorization";
|
||||
|
||||
/**
|
||||
* 获取当前request
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
public static Optional<HttpServletRequest> getRequest() {
|
||||
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
|
||||
if (attributes instanceof ServletRequestAttributes) {
|
||||
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) attributes;
|
||||
return Optional.ofNullable(servletRequestAttributes.getRequest());
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取响应
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
public static Optional<HttpServletResponse> getResponse() {
|
||||
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
|
||||
if (attributes instanceof ServletRequestAttributes) {
|
||||
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) attributes;
|
||||
return Optional.ofNullable(servletRequestAttributes.getResponse());
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有的cookie
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
public static List<Cookie> getCookies() {
|
||||
return getRequest().flatMap(request -> Optional.ofNullable(request.getCookies()))
|
||||
.map(cookies -> Arrays.stream(cookies).collect(Collectors.toList()))
|
||||
.orElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取并过滤cookie
|
||||
*
|
||||
* @param predicate 匹配
|
||||
* @return 结果
|
||||
*/
|
||||
public static List<Cookie> getCookies(Predicate<? super Cookie> predicate) {
|
||||
return getRequest().flatMap(request -> Optional.ofNullable(request.getCookies()))
|
||||
.map(cookies -> Arrays.stream(cookies).filter(predicate).collect(Collectors.toList()))
|
||||
.orElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取鉴权(token)相关的cookie
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
public static Optional<String> getCredential() {
|
||||
return getRequest().map(request -> request.getHeader(AUTHORIZATION_KEY))
|
||||
.filter(DataUtils::isNotBlank)
|
||||
.map(Optional::of)
|
||||
.orElseGet(() -> getCookies(cookie -> AUTHORIZATION_KEY.equals(cookie.getName())).stream()
|
||||
.findAny().map(Cookie::getValue));
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package group.flyfish.rest.utils;
|
||||
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.type.TypeFactory;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* 类型解析工具类
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public final class TypeResolveUtils {
|
||||
|
||||
/**
|
||||
* 使用包装类包装
|
||||
*
|
||||
* @param origin 原类型
|
||||
* @param wrapper 包装类型
|
||||
* @return 结果
|
||||
*/
|
||||
public static Type wrap(Type origin, Class<?> wrapper) {
|
||||
// 构建泛型的返回值
|
||||
TypeFactory typeFactory = JacksonUtil.getMapper().getTypeFactory();
|
||||
if (origin instanceof Class) {
|
||||
return typeFactory.constructParametricType(wrapper, (Class<?>) origin);
|
||||
} else if (origin instanceof JavaType) {
|
||||
return typeFactory.constructParametricType(wrapper, (JavaType) origin);
|
||||
}
|
||||
// 无法解析,未知类型
|
||||
return origin;
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package group.flyfish.rest;
|
||||
|
||||
import group.flyfish.rest.client.RestClient;
|
||||
import group.flyfish.rest.core.builder.MapParamBuilder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@WebAppConfiguration
|
||||
@Slf4j
|
||||
public class RestClientTest {
|
||||
|
||||
private void success(HttpEntity httpEntity) {
|
||||
try {
|
||||
String result =
|
||||
StreamUtils.copyToString(httpEntity.getContent(), Charset.defaultCharset());
|
||||
log.info(result);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGet() {
|
||||
RestClient.create()
|
||||
.get()
|
||||
.url("https://www.baidu.com")
|
||||
.build()
|
||||
.execute(this::success);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPost() {
|
||||
RestClient.create()
|
||||
.post()
|
||||
.url("http://demo.flyfish.group/api/login")
|
||||
.addHeader("Authorization", "Bearer xbsef92x=")
|
||||
.body(MapParamBuilder.builder()
|
||||
.with("username", "admin")
|
||||
.with("password", "123456")
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
.execute(this::success);
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package group.flyfish.rest;
|
||||
|
||||
import group.flyfish.rest.annotation.*;
|
||||
import group.flyfish.rest.container.RestTestContainer;
|
||||
import group.flyfish.rest.domain.TestItem;
|
||||
import group.flyfish.rest.enums.HttpMethod;
|
||||
import group.flyfish.rest.mapping.TestRestResultMapping;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* rest代理测试
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@WebAppConfiguration
|
||||
@SpringBootTest(classes = RestTestContainer.class)
|
||||
@ComponentScan("sys.test")
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RestProxyTest {
|
||||
|
||||
@Resource
|
||||
private TestRestService testRestService;
|
||||
@Resource
|
||||
private TestPostService testPostService;
|
||||
|
||||
/**
|
||||
* 测试入口
|
||||
*/
|
||||
@Test
|
||||
public void test() {
|
||||
List<TestItem> items = testRestService.getCameras("1298064063717781506", "S4NbecfYA1CBGIOB0QDOT4", null);
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("latitude", "");
|
||||
map.put("longitude", "");
|
||||
map.put("radius", "");
|
||||
map.put("type", "1,2,3,4");
|
||||
Object result = testPostService.getResources(map);
|
||||
log.info(items.toString());
|
||||
}
|
||||
|
||||
@RestService(baseUrl = "http://60.221.255.208:18092/api/")
|
||||
@AutoMapping(TestRestResultMapping.class)
|
||||
public interface TestRestService {
|
||||
|
||||
@RestApi(uri = "/video/{platformId}/cameras/all")
|
||||
List<TestItem> getCameras(@RestPathParam String platformId, String regionCode, String name1);
|
||||
}
|
||||
|
||||
|
||||
@RestService(baseUrl = "http://220.194.160.4:8083/interface")
|
||||
@AutoMapping(TestRestResultMapping.class)
|
||||
public interface TestPostService {
|
||||
|
||||
@RestApi(method = HttpMethod.POST, uri = "/getResources")
|
||||
Map<String, List<Map<String, Object>>> getResources(@RestBody Map<String, Object> body);
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package group.flyfish.rest;
|
||||
|
||||
import group.flyfish.rest.annotation.*;
|
||||
import group.flyfish.rest.container.RestTestContainer;
|
||||
import group.flyfish.rest.domain.TestResult;
|
||||
import group.flyfish.rest.mapping.TestRestResultMapping;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@WebAppConfiguration
|
||||
@SpringBootTest(classes = RestTestContainer.class)
|
||||
@ComponentScan("sys.test")
|
||||
@Slf4j
|
||||
@Component
|
||||
public class SimpleProxyTest {
|
||||
|
||||
@Resource
|
||||
private TestSimpleService testSimpleService;
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
log.info(TestResult.class.getMethods()[0].toGenericString());
|
||||
Map<String, Object> values = new HashMap<>();
|
||||
values.put("a", 1);
|
||||
values.put("b", 2);
|
||||
TestResult<Void> query = new TestResult<>();
|
||||
query.setMsg("asdasd");
|
||||
Map<String, Object> result = testSimpleService.getDirectories("123456", "admin", values, query);
|
||||
if (null != result) {
|
||||
log.info(result.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@RestService("dsep")
|
||||
@AutoMapping(TestRestResultMapping.class)
|
||||
public interface TestSimpleService {
|
||||
|
||||
/**
|
||||
* 资产目录列表
|
||||
*
|
||||
* @param token token
|
||||
* @return 结果
|
||||
*/
|
||||
@RestApi("/financial/public/income")
|
||||
Map<String, Object> getDirectories(@RestHeader("Authorization") String token, @RestParam("userId") String userId,
|
||||
Map<String, Object> others, @RestParams TestResult<Void> result);
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package group.flyfish.rest.container;
|
||||
|
||||
import group.flyfish.rest.annotation.EnableRestApiProxy;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = "group.flyfish.rest")
|
||||
@EnableRestApiProxy
|
||||
public class RestTestContainer {
|
||||
|
||||
/**
|
||||
* 唯一启动类
|
||||
*
|
||||
* @param args 程序参数
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(RestTestContainer.class, args);
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package group.flyfish.rest.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 资产目录视图实体
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Data
|
||||
public class DataDirectoryVo {
|
||||
|
||||
private String id;
|
||||
|
||||
private String directoryName;
|
||||
//更新周期
|
||||
private String updateCycleCode;
|
||||
private String updateCycleName;
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package group.flyfish.rest.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 测试项目
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Data
|
||||
public class TestItem {
|
||||
|
||||
private String name;
|
||||
|
||||
private String code;
|
||||
|
||||
private String url;
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package group.flyfish.rest.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 测试用的返回值
|
||||
*
|
||||
* @param <T> 泛型
|
||||
*/
|
||||
@Data
|
||||
public class TestResult<T> {
|
||||
|
||||
private Integer code;
|
||||
|
||||
private String msg;
|
||||
|
||||
private T data;
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package group.flyfish.rest.mapping;
|
||||
|
||||
import group.flyfish.rest.core.exception.RestClientException;
|
||||
import group.flyfish.rest.domain.TestResult;
|
||||
import group.flyfish.rest.utils.TypeResolveUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
@Component
|
||||
public class TestRestResultMapping implements RestResultMapping {
|
||||
|
||||
/**
|
||||
* 模糊的结果映射
|
||||
*
|
||||
* @param result 结果
|
||||
* @return 映射后的结果
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T map(Object result) {
|
||||
if (result instanceof TestResult) {
|
||||
TestResult<?> mapped = (TestResult<?>) result;
|
||||
if (null != mapped.getCode() && (mapped.getCode() == 200 || mapped.getCode() == 0)) {
|
||||
return (T) mapped.getData();
|
||||
}
|
||||
throw new RestClientException("发生了超级异常:" + mapped.getMsg());
|
||||
}
|
||||
return (T) result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析返回类型
|
||||
*
|
||||
* @param resultType 返回类型
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Type resolve(Type resultType) {
|
||||
return TypeResolveUtils.wrap(resultType, TestResult.class);
|
||||
}
|
||||
}
|
9
rest-proxy-core/src/test/resources/application.yml
Normal file
9
rest-proxy-core/src/test/resources/application.yml
Normal file
@ -0,0 +1,9 @@
|
||||
rest:
|
||||
client:
|
||||
connection-timeout: 30s
|
||||
base-url: http://localhost:8089
|
||||
always-trust: true
|
||||
urls:
|
||||
dsep: http://220.194.160.6:8091/jeecg-boot/api
|
||||
bdmp: http://localhost:8081
|
||||
damp: http://60.221.255.208:18092/api
|
Loading…
Reference in New Issue
Block a user