<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          太強(qiáng)大了,F(xiàn)eign對(duì)接YAPI實(shí)現(xiàn)自動(dòng)Mock

          共 3853字,需瀏覽 8分鐘

           ·

          2021-04-25 09:30

          點(diǎn)擊上方藍(lán)色字體,選擇“設(shè)為星標(biāo)”

          回復(fù)”學(xué)習(xí)資料“獲取學(xué)習(xí)寶典


          前面我們介紹了在微服務(wù)架構(gòu)下如何解決單測(cè)時(shí) Mock 的問題,通過 Mock 可以在單測(cè)時(shí)不依賴其他服務(wù)的實(shí)現(xiàn)。在文章最后我也給大家提供了一個(gè)思考題:是不是可以模擬前端對(duì)后端的處理方式,走 Yapi 的 Mock 功能? 這樣就不用自己手動(dòng)的對(duì)每個(gè)接口去 Mock 了。

          首先我們需要定義一個(gè) Mock 的配置類,用于配置哪些遠(yuǎn)程調(diào)用需要進(jìn)行 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 {
          /**
          * 根據(jù)用戶ID查詢用戶
          * @param userId 用戶ID
          * @return
          */
          @GetMapping("/users/{userId}")
          ResponseData<UserResponse> getUser(@PathVariable("userId") Long userId);
          }

          那么資源的格式就是 GET:http://kitty-cloud-user-provider/users/{userId},以 2 個(gè)#作為分隔符,后面接上 Mock 的地址。

          配置格式如下:

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

          配置好了后就需要想辦法對(duì) Feign 進(jìn)行擴(kuò)展,如果調(diào)用的接口在 Mock 配置中,就走 Mock 的地址。

          對(duì) Feign 擴(kuò)展可以參考 Sleuth 中的做法,詳細(xì)代碼就不貼了,文末貼上完整源碼參考地址。

          新增一個(gè)類,繼承 LoadBalancerFeignClient,重寫 execute 方法。需要判斷當(dāng)前執(zhí)行的接口是否在 Mock 名單中,如果在就執(zhí)行 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 來(lái)傳遞的,如果沒有 Restful 風(fēng)格的 API 就不用這樣做了,直接判斷 url 即可。

          Restful 的需要獲取到原始的 uri 定義才行,不然就是/users/1, /users/2 沒辦法判斷是否在 Mock 名單中。

          所以我們得改改底層的代碼,由于項(xiàng)目中用的是 Sentinel 做熔斷,所以在接口調(diào)用的時(shí)候會(huì)先進(jìn)入 Sentinel 的 SentinelInvocationHandler,我們可以在這個(gè)類中進(jìn)行資源名稱的獲取,然后通過 ThreadLocal 進(jìn)行透?jìng)鳌?/p>

          這樣在 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);
          }

          核心代碼并不多,當(dāng)然也省略了如何去替換 LoadBalancerFeignClient 的相關(guān)代碼,完整源碼地址如下:https://github.com/yinjihuan/kitty/tree/feature/1.0/kitty-servicecall/kitty-servicecall-feign/src/main/java/com/cxytiandi/kitty/servicecall/feign

          關(guān)于作者:尹吉?dú)g,簡(jiǎn)單的技術(shù)愛好者,《Spring Cloud微服務(wù)-全棧技術(shù)與案例解析》, 《Spring Cloud微服務(wù) 入門 實(shí)戰(zhàn)與進(jìn)階》作者, 公眾號(hào) 猿天地 發(fā)起人。

          后臺(tái)回復(fù) 學(xué)習(xí)資料 領(lǐng)取學(xué)習(xí)視頻


          如有收獲,點(diǎn)個(gè)在看,誠(chéng)摯感謝

          瀏覽 44
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  爱视频福利 | 国产麻豆一区二区 | A在线大香蕉 | 亚洲爽爆av | 青青草成人在线男人的天堂 |