REST请求代理(Http Interface) 这可能是你用过最爽的http对接神器。
Go to file
2023-04-13 13:43:24 +08:00
rest-proxy-api feat: 升级1.1.2,增加标准注解 2023-04-13 09:30:50 +08:00
rest-proxy-core feat: 升级1.1.2,增加标准注解 2023-04-13 09:30:50 +08:00
.gitignore feat: 初始化项目,迁移原代码,正式开源 2023-01-10 18:19:51 +08:00
img.png feat: 增加文档 2023-04-13 13:43:24 +08:00
LICENSE feat: 上传许可证文件 2023-01-10 18:31:48 +08:00
pom.xml feat: 升级1.1.2,增加标准注解 2023-04-13 09:30:50 +08:00
README.md feat: 增加文档 2023-04-13 13:43:24 +08:00
walllet.jpg feat: 增加文档 2023-04-13 13:43:24 +08:00

什么是Rest Proxy?

Rest Proxy组件是本人在长期的系统对接工作中提炼出的一个可高度复用的组件目前已经被同事们誉为“神器”的存在。这可能是你用过最爽的http对接神器具体用起来有多爽呢做过微服务的同学应该都知道Feign Ref模块吧,本组件就是类似那样的一个东东。

具体是这样的:写一个interface定义几个method然后加上注解完成在业务里直接调用method即可不需要写任何实现类

REST请求代理基于Http Interface

本组件基于SpringBoot2.7.7构建支持完整的AutoConfiguraiton。仅需要极少量配置就可以完成配置。当前最新版本为1.1.2新增了标准REST注解。

特性列表

  • 无感知自动注入只需interface + 注解即可轻松完成对接
  • 优雅的API省去了您的学习烦恼
  • 高性能实现,智能结果处理绑定
  • 比Retrofit更简单的初始化配置并且与Spring无缝集成
  • 支持文件上传下载无感对接
  • 自动解包结果包装类
  • 自动转换参数、请求体
  • 自定义鉴权
  • 动态替换实现(施工中)

快速开始

您可以参考源码中rest-proxy-coretest模块查看测试用例,了解基本用法。 此外,我们提供了example项目您可以直接参考其写法将相关逻辑迁移到您的SpringBoot项目。

具体请查看开源仓库:https://git.flyfish.group/flyfish-group/rest-proxy.git 然后demo的话请切换到example分支。

1. 定义请求源

我们提供了非常灵活的请求源(Base URL)定义方式。

请求源会在接口调用时作为前缀拼接到每个接口上,方便进行路径复用

组件支持全局请求源多请求源以及注解指定请求源三种方式,配置都非常的简单。

1.1 全局请求源

# application.yml
rest:
  client:
    # 通过配置文件指定全局请求源所有未指定请求源的RestService都会以该请求开头
    base-url: https://mybase-source.domain

1.2 多请求源

# application.yml
rest:
  client:
    urls:
      # 定义多个请求源每个请求源有一个唯一的key支持全限定url以及路径
      baidu: https://ug.baidu.com
      yapi: http://yapi.flyfish.group/api

1.3 注解指定请求源

我们支持Service服务级别的请求源以及Method方法级别的请求源指定。

类请求源:

@RestService(baseUrl = "https://ug.baidu.com")
public interface TestService {
   
    @GET("/action")
    String getActionString();
}

方法请求源

@RestService
public interface TestService {
    
    @GET(baseUrl = "https://ug.baidu.com", uri = "/action")
    String getActionString();
}

2. 定义RestService请求服务类

我们提供了@RestService以及一系列的HTTP注解帮助您快速定义好接口无需书写任何的实现。 以下是一个基本的示例供您参考:

/**
 * 使用@RestService如果不指定任何参数
 * 默认会取得全局请求源也就是rest.client.baseUrl的值
 */
@RestService
public interface TestService {

    /**
     * 通过关键字string作为query查询用户列表
     * @param keyword 关键字,会以?keyword={value}的方式传递
     * @return 结果
     */
    @GET("/users")
    List<User> getUsers(String keyword);

    /**
     * 保存用户
     * @param user 请求体,使用@RestBody标记为请求体后参数会自动转换
     * @return 保存结果
     */
    @POST("/users")
    User saveUser(@RestBody User user);

    /**
     * 更新用户信息
     * @param id 用户id路径参数。使用@RestPathParam注解结合花括号{}来标记路径参数
     * @param user 用户数据
     * @return 结果
     */
    @PUT("/users/{id}")
    User updateUser(@RestPathParam("id") Long id, @RestBody User user);

    /**
     * 更新用户的名称
     * @param id 用户id路径参数
     * @param name 用户名称,使用 mergeBody选项将除了指定注解类型的其他参数合并到body中如{name: ''}
     * @return 结果
     */
    @PATCH(value = "/users/{id}", mergedBody = true)
    User updateUser(@RestPathParam("id") Long id, String name);

    /**
     * 删除用户
     * @param id 用户id
     * @return 结果
     */
    @DELETE("/users/{id}")
    User deleteUser(@RestPathParam("id") Long id);
}

如果您需要单独使用某个请求源,请参考:

# application.yml
rest:
  client:
    urls:
      baidu: https://api.baidu.com
      other: http://other.com
@RestService("baidu")
public interface BaiduService {
    
    @POST("/id-cards")
    String updateIdCard(@RestBody IdCardDto idCard);
}

此外,我们还支持文件的上传和下载,请参考

@RestService(baseUrl = "http://localhost:8999", timeout = 500)
public interface TestRestService {

    /**
     * 上传一个预定义的文件使用Multipart包装类并传入?type=xxx以及一个字符串bodytoken=xxx
     * @param file 文件信息
     * @param type 文件类型名称
     * @param token 凭据
     * @return 结果
     */
    @POST("/files")
    FileEntity uploadPart(@RestPart Multipart file, String type, @RestPart("token") Long token);

    /**
     * 使用input stream上传一个文件此外还支持byte[],可以用@RestPart.Filename指定文件名
     * @param file 文件
     * @param name 文件名
     * @return 结果
     */
    @POST("/files")
    FileEntity uploadAnno(@RestPart("fbl") InputStream file, @RestPart.Filename("fbl") String name);

    /**
     * 下载一个文件为byte[]
     * @return 下载结果
     */
    @GET("/files")
    byte[] downloadByte();
}

调用时,只需要如下步骤

@Service
public class Test {
    
    @Resource 
    private TestRestService testRestService;
    
    /**
     * 测试入口
     */
    @Test
    public void test() {
        // 使用file对象包装
        File file = new File("/Users/wangyu/Desktop/2022年终述职报告.docx");
        FileEntity result = testRestService.uploadPart(new Multipart("file", file.getName(), file), "docx", 55L);
        // 使用input stream
        try (InputStream in = Files.newInputStream(file.toPath())) {
            FileEntity entity = testRestService.uploadAnno(in, file.getName());
            System.out.println(entity);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        // 下载
        testRestService.downloadByte();
    }
}

3. 启用接口扫描

最后我们在spring boot启动类上添加注解就可以直接使用了

这里需要手动指定一下要扫描的basePackages组件不经过您的允许是不会扫描其他包的。

package group.flyfish.demo;

import group.flyfish.rest.annotation.EnableRestApiProxy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableRestApiProxy(basePackages = "group.flyfish.demo.service")
public class RestProxyDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(RestProxyDemoApplication.class, args);
    }

}

4. 开始愉快的对接吧!

至此,我们的快速开始教程已经完成。

定制您的rest客户端

我们提供了许多丰富的定制选项来帮助您更好的使用组件。

1. 配置文件定制

以下是一个全量的配置参数说明:

# application.yml
rest:
  client:
    # 总是信任ssl证书
    always-trust: true
    # 全局连接超时时间
    connection-timeout: 30s
    # 全局请求源
    base-url: http://22.11.33.22:5001
    # 多请求源配置key: url
    urls:
      other: https://ug.baidu.com
      yapi: http://yapi.flyfish.group/api

2. 配置类定制

我们还提供了很多配置类用于深度定制您的程序。

2.1 配置自定义钩子

您可以在程序实现RestPropertiesModifier接口并注册为SpringBean来完成运行时对配置的修改。 一般使用场景是我们的多请求源可能是从其他bean、数据库或者缓存读取出来的所以需要通过代码来设置如下

public class UrlMappingAutoConfigure {

    /**
     * 从WorkflowProperties这个bean读取url配置并放入
     * @param workflow 其他bean
     * @return 结果
     */
    @Bean
    public RestPropertiesModifier configureUrls(WorkflowProperties workflow) {
        return properties -> {
            // urls请求源map可以直接操作
            Map<String, String> urls = properties.getUrls();
            String baseUrl = workflow.getEngine().getBaseUrl();
            String businessBaseUrl = workflow.getEngine().getBusinessBaseUrl();
            String controlBaseUrl = workflow.getEngine().getControlBaseUrl();
            // 配置基础路径集合
            FlowableUrlMapping.URLS.forEach((key, url) -> urls.put(key, baseUrl + url));
            FlowableUrlMapping.BUSINESS_URLS.forEach((key, url) -> urls.put(key, businessBaseUrl + url));
            FlowableUrlMapping.CONTROL_URLS.forEach((key, url) -> urls.put(key, controlBaseUrl + url));
        };
    }
}

2.2 配置注入钩子

如果您的程序中需要在一些bean中注入rest客户端的参数类RestClientProperties,请不要直接使用@Resource。 请务必使用PropertiesConfigurable钩子来完成否则会导致项目bean的依赖问题。

以下是框架内部的使用实例:

package group.flyfish.rest.core.factory;

/**
 * 生产httpClient
 *
 * @author wangyu
 */
@Slf4j
public final class HttpClientFactoryBean implements FactoryBean<CloseableHttpClient>, PropertiesConfigurable {

    // 使用非公平锁
    private final ReentrantLock lock = new ReentrantLock();
    // 客户端实例,单例
    private volatile CloseableHttpClient client;
    // 配置,配置没进来就不初始化
    private RestClientProperties properties;
    
    // ...代码省略
    
    @Override
    public Class<?> getObjectType() {
        return CloseableHttpClient.class;
    }

    /**
     * 配置属性,完成初始化
     *
     * @param properties 属性
     */
    @Override
    public void configure(RestClientProperties properties) {
        this.properties = properties;
    }
}

2.3 请求接口鉴权配置

请求鉴权在接口对接时是很重要的一环,我们依旧提供了灵活的方式支持

2.3.1 全局默认鉴权配置

您可以直接声明一个单例的RestAuthProvider实例来指定全局鉴权逻辑。 如果您声明了多个bean则需要通过@Primary告诉具体的注入bean是哪个。 如下:

/**
 * 使用@Component注解注册为bean即可被自动配置
 *
 * @author wangyu
 */
@Component
public class YapiAuthProvider implements RestAuthProvider {

    // yapi控制token
    private static final String token = "e5172a42e62e0497b79e3c7df7b4ec1429399558f9d9d28c0152bd39ba4c217a";

    /**
     * 通过入侵client提供鉴权
     * yapi是使用query鉴权的所以增加query即可
     *
     * @param builder rest客户端构建器
     */
    @Override
    public void provide(RestClientBuilder builder) {
        // 支持添加认证头的方式在此处也可以调用其他rest服务获取接口
        // builder.addHeader("Authorization", "token")
        builder.addParam("token", token);
    }
}

2.3.2 类级别指定鉴权

除了全局鉴权外,还支持类级别的鉴权,只需要您在@RestService注解中指定class即可。

/**
 * yapi服务支持鉴权
 *
 * @author wangyu
 */
@RestService(value = "yapi", authProvider = YapiAuthProvider.class)
public interface YapiService {
    
}

2.4 类级别和方法级别的客户端配置

除了指定全局配置外,您还可以通过注解指定一些请求配置参数,请参考:

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 结果
     */
    int timeout() default -1;

    /**
     * 鉴权提供者类
     *
     * @return 具体实现了RestAuthProvider的类
     */
    Class<?> authProvider() default Object.class;
}

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

    /**
     * 请求uri使用次标注必须指定BaseUrl或者配置现在还不支持
     *
     * @return uri
     */
    @AliasFor("value")
    String uri() default "";

    /**
     * 请求方法
     *
     * @return 结果
     */
    HttpMethod method() default HttpMethod.GET;

    /**
     * 多个参数时使用合并的body
     *
     * @return 结果
     */
    boolean mergedBody() default false;

    /**
     * 可选指定的url不会从默认地址请求
     *
     * @return url
     */
    String url() default "";

    /**
     * 基本路径包含host
     *
     * @return baseUrl
     */
    String baseUrl() default "";

    /**
     * 是否带上认证token
     *
     * @return 结果
     */
    boolean credentials() default false;
}

赞助和打赏

如果您觉得我的项目对您有帮助请star或者请我喝杯️,感谢~ 同时欢迎各位小伙伴提交P/R共同维护这个项目。

微信收款码