feat: 初始化项目,迁移原代码,正式开源

This commit is contained in:
wangyu 2023-01-10 18:19:51 +08:00
commit 8fcb802cfc
63 changed files with 3872 additions and 0 deletions

40
.gitignore vendored Normal file
View 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
View File

@ -0,0 +1,13 @@
# REST请求代理Http Interface
这可能是你用过最爽的http对接神器。
## 特性列表
- 无感知自动注入只需interface + 注解即可轻松完成对接
- 优雅的API省去了您的学习烦恼
- 高性能实现,智能结果处理绑定
- 支持文件上传下载无感对接
- 自动解包结果包装类
- 自动转换参数、请求体
- 自定义鉴权(施工中)
- 动态替换实现(施工中)

89
pom.xml Normal file
View 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
View 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>

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 {
}

View File

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

View File

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

View File

@ -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 {
}

View File

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

View File

@ -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;
}

View File

@ -0,0 +1,9 @@
package group.flyfish.rest.enums;
/**
* Http请求类型
* @author wangyu
*/
public enum HttpMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
}

View File

@ -0,0 +1,10 @@
package group.flyfish.rest.enums;
/**
* 响应类型
*
* @author wangyu
*/
public enum ResponseType {
NORMAL, TEXT, JSON, BINARY, OBJECT
}

View File

@ -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
View 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>

View File

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

View File

@ -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. 支持上传文件FormDataJSON支持自定义无侵入扩展
* 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;
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,12 @@
package group.flyfish.rest.registry;
/**
* 用于标记rest服务代理
*
* @author wangyu
* @deprecated 该类已经过时请使用新版的注解声明
* @see group.flyfish.rest.annotation.RestService
*/
@Deprecated
public interface RestService {
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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) : "");
}
}
}

View File

@ -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));
}
}
}
}
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}
}

View 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