# micro-rest
**Repository Path**: lboot/micro-rest
## Basic Information
- **Project Name**: micro-rest
- **Description**: 使用注解的方式快速构建微服务/HTTP请求接口,基于动态代理和反射实现
- **Primary Language**: Java
- **License**: MPL-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 2
- **Forks**: 0
- **Created**: 2023-10-23
- **Last Updated**: 2025-01-22
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# MicroRest
`MircoRest`是一款基于`okhttp`构建的 `http`客户端请求框架,优雅适配了微服务请求,它能够将 HTTP 的所有请求信息(包括 URL、Header 以及 Body 等信息)绑定到您自定义的 Interface 方法上,支持上下文穿透、请求参数动态修改,SSE请求支持,能够通过调用本地接口方法的方式发送 HTTP 请求。
其设计思想基于动态代理。
## 特性
- [x] 自动类型转化,接口返回值自动转化为函数接收对象类型(使用Map fillBean)
- [x] 传统`HTTP` 请求支持, `@Get` `@Post` `@Put` `@Delete` 支持
- [x] 文件上传支持(`MultipartFile`或本地文件)
- [x] 微服务请求支持,`@MicroGet` `@MicroPost` `@MicroPut` `@MicroDelete` 支持,默认支持 `NacOS`,支持自定义拓展
- [x] `SSE`请求支持,`@SseGet` `@SsePost`支持,类`GPT`请求实现转发
- [x] 请求参数自定义,`@PathVar` 自定义路径参数 , `@Query` 自定义查询参数
- [x] 请求头自定义 ,`@Headers` 请求头支持,多种构建方式
- [x] 请求体自定义, `@Body` 请求体支持,多种构建方式
- [x] 自定义请求客户端 `MicroRestClient`
- [x] 支持装饰器模式 ,传递请求头和请求体
- [x] 请求超时配置
## 安装配置
### 安装
需要配置 `jitpack`作为镜像源,在`pom`文件中引入
```xml
jitpack.io
https://www.jitpack.io
```
引入 `micro-rest` 依赖,版本号查看右侧发行版版本
```xml
com.gitee.lboot
micro-rest
${version}
```
### 配置
该框架默认支持并实现了基于 `nacos`的微服务发现机制,需要配置如下
> 不需要引入 nacos 依赖,是基于 openAPI 构建实现
```properties
#################### 服务注册发现 #######################
nacos.discovery.server-addr=127.0.0.1:8848
nacos.discovery.auto-register=true
nacos.discovery.enabled=true
```
支持拓展其他的服务发现,只需要自定义拓展实现`ServiceResolution`
## 快速上手
> 开放接口提供文章: https://juejin.cn/post/7041461420818432030
对于需要自定义的接口,需要用 `@MicroRest`标记为需要动态代理实现的接口,程序编译时会自动构建对应的请求方法。对于请求返回的结果,程序会自动读取接口参数类型并构建转化函数实现数据的转化。
### 请求地址构建
构建一个请求,最重要的是请求地址的构建,如下注解实现了请求路径参数和查询参数的构建
#### @PathVar
> 路径参数构建
`@PathVar("key")` 可以指定路径参数名称,如果没有指定,则按照当前参数实际名称进行替换,例如下图两种定义方式都可以。
```java
@MicroRest
public interface TestPostApi {
@Get("http://jsonplaceholder.typicode.com/posts/{id}")
PostVO getPost(@PathVar Long id);
@Get("http://jsonplaceholder.typicode.com/posts/{id}")
PostVO getPost(@PathVar("id") Long xx);
}
```
#### @Query
> 查询参数构建
其参数替换有如下规则:
1. 指定参数为基础数据类型(原子性数据,不可再分割,例如 `Integer, Long, String `),支持使用`@Query("key") ` 指定参数名称,或使用 `@Query` 使用当前参数实际名称,下面两种定义方式效果一致。
```java
@MicroRest
public interface TestPostApi {
@Get("http://jsonplaceholder.typicode.com/posts")
List getPosts(@Query("userId") Long xx);
@Get("http://jsonplaceholder.typicode.com/posts")
List getPosts(@Query Long userId);
}
```
2. 指定参数非基础数据类型,例如`Map,用户自定义类`, 仅支持 `@Query`,支持自动转化参数,下面定义效果与上面一致。
> 用户自定义查询参数
```java
class QueryParams{
String userId;
}
```
```java
@MicroRest
public interface TestPostApi {
@Get("http://jsonplaceholder.typicode.com/posts")
List getPosts(@Query QueryParams params);
}
```
#### #{}
> 模板内容替换
目前仅支持读取`application.properties`配置文件中的配置项的值,并实现替换
> application.properties
```
openai.chat.host=https://api.openai-proxy.com
```
```java
@MicroRest
public interface MicroRestChatApi {
@SsePost(value = "#{openai.chat.host}/v1/chat/completions", converter = ChatConverter.class)
StreamResponse chatCompletions(@Headers Map headers, @Body Map params);
}
```
### 请求头构建
#### @Headers
> 请求头信息构建
其参数替换有如下规则:
1. 指定参数为基础数据类型(原子性数据,不可再分割,例如 `Integer, Long, String `),支持使用`@Headers("key") ` 指定参数名称,或使用 `@Headers` 使用当前参数实际名称,下面两种定义方式效果一致。
```java
@MicroRest
public interface TestPostApi {
@Get("http://jsonplaceholder.typicode.com/posts")
List getPosts(@Headers("token") String token);
@Get("http://jsonplaceholder.typicode.com/posts")
List getPosts(@Headers String token);
}
```
2. 指定参数非基础数据类型,例如`Map,用户自定义类`, 仅支持 `@Headers`,支持自动转化参数,下面定义效果与上面一致。
> 自定义实体类
```java
class CustomHeaders{
String token = "xxx";
}
```
> Map
```json
{
"Content-Type":"application/x-www-form-urlencoded"
}
```
> 使用举例
```java
@MicroRest
public interface TestPostApi {
@Get("http://jsonplaceholder.typicode.com/posts")
List getPosts(@Headers CustomHeaders headers);
@Get("http://jsonplaceholder.typicode.com/posts")
List getPosts(@Headers Map headers);
}
```
3. 原子性数据参数优先级大于自定义类或`Map`, 会覆盖其中的数据。
#### @Anno headers
例如`@Get`, `@MicroPost`等一些列注解,都支持请求头参数配置,其配置形式如下:
```java
@MicroRest
public interface TestPostApi {
@Post(url = "http://jsonplaceholder.typicode.com/posts",
headers = {
"Content-Type:application/x-www-form-urlencoded;charset=utf-8"
})
PostVO createPost(@Headers Map headers);
}
```
这种方式一般用于固定数值的请求头,需要动态变化的,采用上面的方式进行组装。
支持模板替换,自动匹配替换`application.properties`指定的数据项
```java
@SsePost(value = "#{openai.chat.host}/v1/chat/completions",
headers = {"Authorization:Bearer #{openai.chat.key}"},
converter = ChatConverter.class)
StreamResponse chatCompletions(@Body Map params);
```
其中` headers = {"Authorization:Bearer #{openai.chat.key}"}`就是模板请求头用法
### 请求体构建
#### @Body
> 请求体构建
该参数用法和`@Query`, `@Headers` 都很相似
1. 指定参数为基础数据类型(原子性数据,不可再分割,例如 `Integer, Long, String `),支持使用`@Body("key") ` 指定参数名称,或使用 `@Body` 使用当前参数实际名称。
2. 指定参数非基础数据类型,例如`Map,用户自定义类`, 仅支持 `@Body`,支持自动转化参数。
3. 原子性数据参数优先级大于自定义类或`Map`, 会覆盖其中的数据。
```java
@MicroRest
public interface TestPostApi {
@Post("http://jsonplaceholder.typicode.com/posts")
Object createPost(@Body PostVO postVO);
}
```
### 请求透传
#### @Decorator
> 请求透传
被该注解标记的方法,会根据配置属性,自动读取请求上下文中的请求头和请求体
```java
@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Decorator {
// 是否读取请求头
boolean withHeader() default true;
// 是否读取请求体
boolean withBody() default true;
// 装饰器默认实现 --> 请求头和请求体读取,支持自定义
Class extends ProxyContextDecorator> value() default DefaultProxyContextDecorator.class;
}
```
> 自定义
通过继承抽象类 `ProxyContextDecorator`并重写方法实现,在使用注解时指定自定义实现类
```java
public abstract class ProxyContextDecorator{
public Map readHeader(){
HashMap header = new HashMap<>();
HttpServletRequest request =((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
Enumeration headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()){
String headerName = headerNames.nextElement();
header.put(headerName,request.getHeader(headerName));
}
return header;
}
public Map readBody(){
HashMap body = new HashMap<>();
HttpServletRequest request =((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
Enumeration bodyNames = request.getParameterNames();
while (bodyNames.hasMoreElements()){
String paramKey = bodyNames.nextElement();
body.put(paramKey,request.getParameter(paramKey));
}
return body;
}
}
```
### 响应拦截
#### @ResponseHandler
> 响应拦截
```java
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseHandler {
// 拦截处理器默认实现 -->
Class extends ProxyResponseHandler> value() default ProxyResponseHandler.class;
}
```
> 自定义
通过继承抽象类 `ProxyResponseHandler`并重写方法实现,在使用注解时指定自定义实现类
```java
public abstract class ProxyResponseHandler{
public String onSuccess(String body){
return body;
}
@SneakyThrows
public MicroRestException onFailure(Response response){
String message = null;
if (response.body() != null) {
message = response.body().string();
}
if (Validator.isEmpty(message)){
message = response.message();
}
Integer code = response.code();
return new MicroRestException(code,message);
}
}
```
### 文件上传支持
通过对 `@Post`,`@MicroPost`请求头配置实现
1. 请求头配置为 ` Content-Type:multipart/form-data`
```java
@Post(value = "#{openai.chat.host}/v1/files",headers = {
"Content-Type:multipart/form-data",
"Authorization: Bearer #{openai.chat.key}"
})
Map uploadFile(@Body ChatFileParams params);
```
2. 参数支持`MultipartFile`或`File`
```java
@Data
public class ChatFileParams {
File file;
String purpose;
}
```
```java
@Data
public class ChatFileParams {
MultipartFile file;
String purpose;
}
```
如果没有其余附加信息,可以只上传文件
```java
@Post(value = "http://localhost:8080/v1/files",headers = {
"Content-Type:multipart/form-data",
})
Map uploadFile(@Body("fileKey") File file);
```
```java
@Post(value = "http://localhost:8080/v1/files",headers = {
"Content-Type:multipart/form-data",
})
Map uploadFile(@Body("fileKey") MultipartFile file);
```
### REST请求支持
#### @Post
> 作用域:方法
#### @Put
> 作用域:方法
#### @Get
> 作用域:方法
#### @Delete
> 作用域:方法
#### 注解属性
上述注解注解属性一致:
```java
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XXX {
@AliasFor("url")
String value() default "";
@AliasFor("value")
String url() default "";
// 默认请求头配置
String[] headers() default {};
// 超时时间配置
int connectTimeout() default 10;
int readTimeout() default 10;
int writeTimeout() default 10;
}
```
### 微服务请求支持
#### @MicroPost
> 作用域:方法
#### @MicroPut
> 作用域:方法
#### @MicroGet
> 作用域:方法
#### @MicroDelete
> 作用域:方法
#### 注解属性
上述注解属性项一致
```java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MicroXXX {
// 分组名称,不传递则为默认分组
String groupName() default "";
// 微服务名称
String serviceName() default "";
// 请求路径
String path() default "";
// 请求头信息
String[] headers() default {};
}
```
#### 基础使用
项目已经基于`OpenApi`实现了对`NacOS`微服务请求的支持,指定服务名称,构建请求时,会自动将服务名称置换为对应的请求地址
```java
@MicroRest
public interface AuthApi {
@MicroGet(serviceName = "auth-hrm",path = "/system/user/info")
AuthInfo getUserInfo(@Headers("token") String token);
@MicroGet(serviceName = "auth-hrm", path = "/system/users/{id}")
List