MircoRest
是一款基于okhttp
构建的 http
客户端请求框架,优雅适配了微服务请求,它能够将 HTTP 的所有请求信息(包括 URL、Header 以及 Body 等信息)绑定到您自定义的 Interface 方法上,支持上下文穿透、请求参数动态修改,SSE请求支持,能够通过调用本地接口方法的方式发送 HTTP 请求。
其设计思想基于动态代理。
-
自动类型转化,接口返回值自动转化为函数接收对象类型(使用Map fillBean)
-
传统
HTTP
请求支持,@Get
@Post
@Put
@Delete
支持 -
文件上传支持(
MultipartFile
或本地文件) -
微服务请求支持,
@MicroGet
@MicroPost
@MicroPut
@MicroDelete
支持,默认支持NacOS
,支持自定义拓展 -
SSE
请求支持,@SseGet
@SsePost
支持,类GPT
请求实现转发 -
请求参数自定义,
@PathVar
自定义路径参数 ,@Query
自定义查询参数 -
请求头自定义 ,
@Headers
请求头支持,多种构建方式 -
请求体自定义,
@Body
请求体支持,多种构建方式 -
自定义请求客户端
MicroRestClient
-
支持装饰器模式 ,传递请求头和请求体
-
请求超时配置
需要配置 jitpack
作为镜像源,在pom
文件中引入
<!--jitpack-->
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://www.jitpack.io</url>
</repository>
</repositories>
引入 micro-rest
依赖,版本号查看右侧发行版版本
<!--微服务请求-->
<dependency>
<groupId>com.gitee.lboot</groupId>
<artifactId>micro-rest</artifactId>
<version>${version}</version>
</dependency>
该框架默认支持并实现了基于 nacos
的微服务发现机制,需要配置如下
不需要引入 nacos 依赖,是基于 openAPI 构建实现
#################### 服务注册发现 #######################
nacos.discovery.server-addr=127.0.0.1:8848
nacos.discovery.auto-register=true
nacos.discovery.enabled=true
支持拓展其他的服务发现,只需要自定义拓展实现ServiceResolution
对于需要自定义的接口,需要用 @MicroRest
标记为需要动态代理实现的接口,程序编译时会自动构建对应的请求方法。对于请求返回的结果,程序会自动读取接口参数类型并构建转化函数实现数据的转化。
构建一个请求,最重要的是请求地址的构建,如下注解实现了请求路径参数和查询参数的构建
路径参数构建
@PathVar("key")
可以指定路径参数名称,如果没有指定,则按照当前参数实际名称进行替换,例如下图两种定义方式都可以。
@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);
}
查询参数构建
其参数替换有如下规则:
-
指定参数为基础数据类型(原子性数据,不可再分割,例如
Integer, Long, String
),支持使用@Query("key")
指定参数名称,或使用@Query
使用当前参数实际名称,下面两种定义方式效果一致。@MicroRest public interface TestPostApi { @Get("http://jsonplaceholder.typicode.com/posts") List<PostVO> getPosts(@Query("userId") Long xx); @Get("http://jsonplaceholder.typicode.com/posts") List<PostVO> getPosts(@Query Long userId); }
-
指定参数非基础数据类型,例如
Map,用户自定义类
, 仅支持@Query
,支持自动转化参数,下面定义效果与上面一致。用户自定义查询参数
class QueryParams{ String userId; }
@MicroRest public interface TestPostApi { @Get("http://jsonplaceholder.typicode.com/posts") List<PostVO> getPosts(@Query QueryParams params); }
模板内容替换
目前仅支持读取application.properties
配置文件中的配置项的值,并实现替换
application.properties
openai.chat.host=https://api.openai-proxy.com
@MicroRest
public interface MicroRestChatApi {
@SsePost(value = "#{openai.chat.host}/v1/chat/completions", converter = ChatConverter.class)
StreamResponse chatCompletions(@Headers Map<String, Object> headers, @Body Map<String,Object> params);
}
请求头信息构建
其参数替换有如下规则:
-
指定参数为基础数据类型(原子性数据,不可再分割,例如
Integer, Long, String
),支持使用@Headers("key")
指定参数名称,或使用@Headers
使用当前参数实际名称,下面两种定义方式效果一致。@MicroRest public interface TestPostApi { @Get("http://jsonplaceholder.typicode.com/posts") List<PostVO> getPosts(@Headers("token") String token); @Get("http://jsonplaceholder.typicode.com/posts") List<PostVO> getPosts(@Headers String token); }
-
指定参数非基础数据类型,例如
Map,用户自定义类
, 仅支持@Headers
,支持自动转化参数,下面定义效果与上面一致。自定义实体类
class CustomHeaders{ String token = "xxx"; }
Map
{ "Content-Type":"application/x-www-form-urlencoded" }
使用举例
@MicroRest public interface TestPostApi { @Get("http://jsonplaceholder.typicode.com/posts") List<PostVO> getPosts(@Headers CustomHeaders headers); @Get("http://jsonplaceholder.typicode.com/posts") List<PostVO> getPosts(@Headers Map<String,Object> headers); }
-
原子性数据参数优先级大于自定义类或
Map
, 会覆盖其中的数据。
例如@Get
, @MicroPost
等一些列注解,都支持请求头参数配置,其配置形式如下:
@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<String,Object> headers);
}
这种方式一般用于固定数值的请求头,需要动态变化的,采用上面的方式进行组装。
支持模板替换,自动匹配替换application.properties
指定的数据项
@SsePost(value = "#{openai.chat.host}/v1/chat/completions",
headers = {"Authorization:Bearer #{openai.chat.key}"},
converter = ChatConverter.class)
StreamResponse chatCompletions(@Body Map<String,Object> params);
其中 headers = {"Authorization:Bearer #{openai.chat.key}"}
就是模板请求头用法
请求体构建
该参数用法和@Query
, @Headers
都很相似
-
指定参数为基础数据类型(原子性数据,不可再分割,例如
Integer, Long, String
),支持使用@Body("key")
指定参数名称,或使用@Body
使用当前参数实际名称。 -
指定参数非基础数据类型,例如
Map,用户自定义类
, 仅支持@Body
,支持自动转化参数。 -
原子性数据参数优先级大于自定义类或
Map
, 会覆盖其中的数据。@MicroRest public interface TestPostApi { @Post("http://jsonplaceholder.typicode.com/posts") Object createPost(@Body PostVO postVO); }
请求透传
被该注解标记的方法,会根据配置属性,自动读取请求上下文中的请求头和请求体
@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
并重写方法实现,在使用注解时指定自定义实现类
public abstract class ProxyContextDecorator{
public Map<String,Object> readHeader(){
HashMap<String,Object> header = new HashMap<>();
HttpServletRequest request =((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()){
String headerName = headerNames.nextElement();
header.put(headerName,request.getHeader(headerName));
}
return header;
}
public Map<String,Object> readBody(){
HashMap<String,Object> body = new HashMap<>();
HttpServletRequest request =((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
Enumeration<String> bodyNames = request.getParameterNames();
while (bodyNames.hasMoreElements()){
String paramKey = bodyNames.nextElement();
body.put(paramKey,request.getParameter(paramKey));
}
return body;
}
}
响应拦截
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseHandler {
// 拦截处理器默认实现 -->
Class<? extends ProxyResponseHandler> value() default ProxyResponseHandler.class;
}
自定义
通过继承抽象类 ProxyResponseHandler
并重写方法实现,在使用注解时指定自定义实现类
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
请求头配置实现
- 请求头配置为
Content-Type:multipart/form-data
@Post(value = "#{openai.chat.host}/v1/files",headers = {
"Content-Type:multipart/form-data",
"Authorization: Bearer #{openai.chat.key}"
})
Map<String,Object> uploadFile(@Body ChatFileParams params);
- 参数支持
MultipartFile
或File
@Data
public class ChatFileParams {
File file;
String purpose;
}
@Data
public class ChatFileParams {
MultipartFile file;
String purpose;
}
如果没有其余附加信息,可以只上传文件
@Post(value = "http://localhost:8080/v1/files",headers = {
"Content-Type:multipart/form-data",
})
Map<String,Object> uploadFile(@Body("fileKey") File file);
@Post(value = "http://localhost:8080/v1/files",headers = {
"Content-Type:multipart/form-data",
})
Map<String,Object> uploadFile(@Body("fileKey") MultipartFile file);
作用域:方法
作用域:方法
作用域:方法
作用域:方法
上述注解注解属性一致:
@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;
}
作用域:方法
作用域:方法
作用域:方法
作用域:方法
上述注解属性项一致
@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
微服务请求的支持,指定服务名称,构建请求时,会自动将服务名称置换为对应的请求地址
@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<Object> getUserById(@PathVar("id") String id);
}
通过继承实现 ServiceResolution
接口,可以适配多种服务注册发现中心,以本项目默认实现举例:
MicroRest
实现了对SSE
请求的支持,限制返回类型必须为StreamResponse
一文读懂即时更新方案:SSE - 掘金 (juejin.cn)
以ChatGPT
请求转发为例
@MicroRest
public interface MicroRestChatApi {
@SsePost(value = "#{openai.chat.host}/v1/chat/completions", converter = ChatConverter.class)
StreamResponse chatCompletions(@Headers Map<String, Object> headers, @Body Map<String,Object> params);
}
Controller 写法
MicroRestChatApi microRestChatApi;
@GetMapping(value = "stream/chat/{chatId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
@ApiOperation(value = "流聊天")
@SneakyThrows
public StreamResponse doStreamChat(@PathVariable("chatId") String chatId){
// 构建请求头和请求体
return microRestChatApi.chatCompletions(headers, paramMap);
}
作用域: 方法
作用域:方法
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SseXXX {
@AliasFor("url")
String value() default "";
@AliasFor("value")
String url() default "";
String[] headers() default {};
// 终止信号
String signal() default "DONE";
// 信息类型转换默认实现类
Class<? extends SseMessageConverter> converter() default DefaultSseMessageConverter.class;
// 超时时间配置
int connectTimeout() default 10;
int readTimeout() default 600;
int writeTimeout() default 50;
}
类似 @PathVar
用法,不需要指定内部参数
作用域:参数
SSE
请求需要指定信道ID,如果没有指定则自动构建随机信道ID
@MicroRest
public interface MicroRestChatApi {
@SsePost(value = "#{openai.chat.host}/v1/chat/completions", converter = ChatConverter.class)
StreamResponse chatCompletions(@Headers Map<String, Object> headers, @Body Map<String,Object> params, @SseSocketId String socketId);
}
监听钩子
通过拓展实现对应服务实现对应的业务逻辑
public interface SseHookService {
/**
* 监听连接状态做对应处理
* @param socketId
*/
void onConnect(String socketId);
/**
* 监听连接错误状态做处理
* @param socketId
*/
void onError(String socketId);
/**
* 监听完成状态做对应处理
* @param socketId
* @param msg
*/
void onCompletion(String socketId,String msg);
}
通过Hook
可以实现对SSE
状态的监听处理,除此之外,提供了事件驱动的方式实现监听(高度解耦),目前支持了消息完成监听
SseMessageCompleteEvent
SSE
通信完成事件,msg
为最后全部数据流经SseMessageConverter
转化后组成的完整内容
@Slf4j
@Getter
public class SseMessageCompleteEvent extends ApplicationEvent {
String msg;
public SseMessageCompleteEvent(Object source, String msg) {
super(source);
this.msg = msg;
}
public SseMessageCompleteEvent(Object source, Clock clock) {
super(source, clock);
}
}
监听示例
可以设置 @Async
异步处理
@Slf4j
@Component
public class MicroRestTestListener {
// @Async
@EventListener(SseMessageCompleteEvent.class)
public void SseMessageCompleteEventListener(SseMessageCompleteEvent event){
String msg = event.getMsg();
log.info("SSE传送消息为:{}", msg);
}
}
基于okhttp
构建请求比较复杂,基于okhttp
封装MircoRestClient
,可以非常方便的构建请求,实现用户自己的自定义请求。
用法举例:
Response response = new MicroRestClient()
.url("http://localhost:8001")
.addQuery("serviceName",serviceName)
.addQuery("groupName", groupName)
.execute();
ResponseBody responseBody = response.body();
支持方法:
方法 | 备注 | 返回结果 |
---|---|---|
header(Map<String,Object> header) | 设置请求头 | MicroRestClient |
addHeader(String key, Object val) | 设置请求头 | MicroRestClient |
body(Map<String,Object> body) | 设置请求体 | MicroRestClient |
addBody(String key, Object val) | 设置请求体 | MicroRestClient |
query(Map<String,Object> query) | 设置请求参数 | MicroRestClient |
addQuery(String key, Object val) | 设置请求参数 | MicroRestClient |
method(HttpMethod httpMethod) | 设置请求方法 | MicroRestClient |
method(String method) | 设置请求方法 | MicroRestClient |
url(String url) | 设置请求地址 | MicroRestClient |
sse() | 设置开启SSE |
MicroRestClient |
execute() | 请求执行 | Response:okhttp3 |
getRequest() | 获取当前请求信息 | Request:okhttp3 |