太强大了,Feign对接YAPI实现自动Mock

猿天地

共 3853字,需浏览 8分钟

 ·

2021-04-25 09:30

点击上方蓝色字体,选择“设为星标”

回复”学习资料“获取学习宝典


前面我们介绍了在微服务架构下如何解决单测时 Mock 的问题,通过 Mock 可以在单测时不依赖其他服务的实现。在文章最后我也给大家提供了一个思考题:是不是可以模拟前端对后端的处理方式,走 Yapi 的 Mock 功能? 这样就不用自己手动的对每个接口去 Mock 了。

首先我们需要定义一个 Mock 的配置类,用于配置哪些远程调用需要进行 Mock。

@Data
@Configuration
@ConfigurationProperties(prefix = "mock")
public class ApiMockProperties {
/**
* 资源:mock地址
* 格式:GET:http://user-provider/user/{userId}##http://xxx.com/mock/api/1001
*/
private List<String> apis;
public String getMockApi(String resource) {
if (CollectionUtils.isEmpty(apis)) {
return null;
}
Map<String, String> apiMap = apis.stream().collect(Collectors.toMap(s -> {
return s.split("##")[0];
}, s -> s.split("##")[1]));
return apiMap.get(resource);
}
}

比如我们的 Feign Client 定义如下:

@FeignClient(name = "kitty-cloud-user-provider")
public interface UserRemoteService {
/**
* 根据用户ID查询用户
* @param userId 用户ID
* @return
*/
@GetMapping("/users/{userId}")
ResponseData<UserResponse> getUser(@PathVariable("userId") Long userId);
}

那么资源的格式就是 GET:http://kitty-cloud-user-provider/users/{userId},以 2 个#作为分隔符,后面接上 Mock 的地址。

配置格式如下:

mock.apis[0]=GET:http://kitty-cloud-user-provider/users/{userId}##http://yapi.cxytiandi.com/mock/74/v1/user

配置好了后就需要想办法对 Feign 进行扩展,如果调用的接口在 Mock 配置中,就走 Mock 的地址。

对 Feign 扩展可以参考 Sleuth 中的做法,详细代码就不贴了,文末贴上完整源码参考地址。

新增一个类,继承 LoadBalancerFeignClient,重写 execute 方法。需要判断当前执行的接口是否在 Mock 名单中,如果在就执行 Mock 操作。

public class MockLoadBalancerFeignClient extends LoadBalancerFeignClient {
private ApiMockProperties apiMockProperties;
public MockLoadBalancerFeignClient(Client delegate, CachingSpringLoadBalancerFactory lbClientFactory,
SpringClientFactory clientFactory, ApiMockProperties apiMockProperties) {
super(delegate, lbClientFactory, clientFactory);
this.apiMockProperties = apiMockProperties;
}
@Override
public Response execute(Request request, Request.Options options) throws IOException {
RequestContext currentContext = ContextHolder.getCurrentContext();
String feignCallResourceName = currentContext.get("feignCallResourceName");
String mockApi = apiMockProperties.getMockApi(feignCallResourceName);
if (StringUtils.hasText(feignCallResourceName) && StringUtils.hasText(mockApi)) {
Request newRequest = Request.create(request.httpMethod(),
mockApi, request.headers(), request.requestBody());
return super.getDelegate().execute(newRequest, options);
} else {
return super.execute(request, options);
}
}
}

feignCallResourceName 是通过 ThreadLocal 来传递的,如果没有 Restful 风格的 API 就不用这样做了,直接判断 url 即可。

Restful 的需要获取到原始的 uri 定义才行,不然就是/users/1, /users/2 没办法判断是否在 Mock 名单中。

所以我们得改改底层的代码,由于项目中用的是 Sentinel 做熔断,所以在接口调用的时候会先进入 Sentinel 的 SentinelInvocationHandler,我们可以在这个类中进行资源名称的获取,然后通过 ThreadLocal 进行透传。

这样在 LoadBalancerFeignClient 中就可以获取资源名然后去 Mock 名单中判断了。

String resourceName = methodMetadata.template().method().toUpperCase()
+ ":" + hardCodedTarget.url() + methodMetadata.template().path();
RequestContext requestContext = ContextHolder.getCurrentContext();
requestContext.add("feignCallResourceName", resourceName);
Entry entry = null;
try {
ContextUtil.enter(resourceName);
entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
result = methodHandler.invoke(args);
}

核心代码并不多,当然也省略了如何去替换 LoadBalancerFeignClient 的相关代码,完整源码地址如下:https://github.com/yinjihuan/kitty/tree/feature/1.0/kitty-servicecall/kitty-servicecall-feign/src/main/java/com/cxytiandi/kitty/servicecall/feign

关于作者:尹吉欢,简单的技术爱好者,《Spring Cloud微服务-全栈技术与案例解析》, 《Spring Cloud微服务 入门 实战与进阶》作者, 公众号 猿天地 发起人。

后台回复 学习资料 领取学习视频


如有收获,点个在看,诚挚感谢

浏览 35
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报