spring boot / cloud (八) 使用RestTemplate來構建遠程調用服務

来源:http://www.cnblogs.com/itkk/archive/2017/09/04/7473847.html
-Advertisement-
Play Games

spring boot / cloud (八) 使用RestTemplate來構建遠程調用服務 前言 上周因家裡突發急事,請假一周,故博客沒有正常更新 RestTemplate介紹: RestTemplate是spring框架中自帶的rest客戶端工具類,具有豐富的API,並且在spring clo ...


spring boot / cloud (八) 使用RestTemplate來構建遠程調用服務

前言

上周因家裡突發急事,請假一周,故博客沒有正常更新

RestTemplate介紹:

RestTemplate是spring框架中自帶的rest客戶端工具類,具有豐富的API,並且在spring cloud中,標記@LoadBalanced註解,可以實現客戶端負載均衡的rest調用.

思路

RestTemplate雖然提供了豐富的API,但是這些API過於底層,如果不稍加控制,讓開發人員隨意使用,那後續的代碼也將會變的五花八門,難以維護.

同時,當系統規模大了之後,將會有更多的服務,並且服務之間的調用關係也將更加複雜,如果不進行管控治理的話,同樣,項目同期也將越來越不可控,

最後,服務間調用也需要有明確的許可權認證機制,最好是能通過配置的方式來明確,哪些服務可以調用那些服務.從而來把控項目的複雜度.

本文將從以下幾點來提供一個解決問題的思路:

實現

1.實現許可權配置

1.定義Application元數據

public class ApplicationMeta implements Serializable {
  //ID
  private static final long serialVersionUID = 1L;
  //服務ID
  private String serviceId;
  //私鑰
  private String secret;
  //許可權
  private String purview;
  //所有服務的調用許可權(優先判定)
  private Boolean all = false;
  //禁止服務調用
  private Boolean disabled = false;
  //描述
  private String description;
}

2.定義Service元數據

public class ServiceMeta implements Serializable {
  //ID
  private static final long serialVersionUID = 1L;
  //應用名稱
  private String owner;
  //地址
  private String uri;
  //服務方法
  private String method;
  //是否HTTPS
  private Boolean isHttps = false;
  //描述
  private String description;

3.定義RmsProperties類

@Component
@ConfigurationProperties(prefix = "org.itkk.rms.properties")
public class RmsProperties implements Serializable {
  //ID
  private static final long serialVersionUID = 1L;
  //應用清單(應用名稱 : 應用地址)
  private Map<String, ApplicationMeta> application;
  //服務路徑(服務編號 : 服務元數據)
  private Map<String, ServiceMeta> service;

4.在properties文件中進行配置

#定義了一個叫udf-demo(跟spring boot的應用ID一致),設置了私鑰,以及可調用的服務
org.itkk.rms.properties.application.udf-demo.serviceId=127.0.0.1:8080
org.itkk.rms.properties.application.udf-demo.secret=ADSFHKW349546RFSGF
org.itkk.rms.properties.application.udf-demo.purview=FILE_3
org.itkk.rms.properties.application.udf-demo.all=false
org.itkk.rms.properties.application.udf-demo.disabled=false
org.itkk.rms.properties.application.udf-demo.description=sample application

#定義了一個叫FILE_3的服務,後續使用這個服務編號進行調用即可
org.itkk.rms.properties.service.FILE_3.owner=udf-demo
org.itkk.rms.properties.service.FILE_3.uri=/service/file/download
org.itkk.rms.properties.service.FILE_3.method=POST
org.itkk.rms.properties.service.FILE_3.isHttps=false
org.itkk.rms.properties.service.FILE_3.description=文件下載

2.實現許可權校驗

1.定義RmsAuthHandlerInterceptor攔截器

public class RmsAuthHandlerInterceptor implements HandlerInterceptor {
  //環境標識
  private static final String DEV_PROFILES = "dev";
  //配置
  @Autowired
  private RmsProperties rmsProperties;
  //環境變數
  @Autowired
  private Environment env;
  
  @Override
  public boolean preHandle(HttpServletRequest request, 
      HttpServletResponse response,
      Object handler) {
      
      .......
      
  }
}

2.完善preHandle方法-取出認證信息

    String rmsApplicationName = request.getHeader(Constant.HEADER_RMS_APPLICATION_NAME_CODE);
    if (StringUtils.isBlank(rmsApplicationName)) {
      rmsApplicationName = request.getParameter(Constant.HEADER_RMS_APPLICATION_NAME_CODE);
    }
    //獲取認證信息(sign)
    String rmsSign = request.getHeader(Constant.HEADER_RMS_SIGN_CODE);
    if (StringUtils.isBlank(rmsSign)) {
      rmsSign = request.getParameter(Constant.HEADER_RMS_SIGN_CODE);
    }
    //獲取認證信息(服務代碼)
    String rmsServiceCode = request.getHeader(Constant.HEADER_SERVICE_CODE_CODE);
    if (StringUtils.isBlank(rmsServiceCode)) {
      rmsServiceCode = request.getParameter(Constant.HEADER_SERVICE_CODE_CODE);
    }
    //獲取請求地址
    String url = request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE).toString();
    //獲取請求方法
    String method = request.getMethod();

3.完善preHandle方法-校驗

    //判斷環境(開發環境無需校驗)
    if (!DEV_PROFILES.equals(env.getProperty("spring.profiles.active"))) {
      //判斷是否缺少認證信息
      if (StringUtils.isBlank(rmsApplicationName) || StringUtils.isBlank(rmsSign)
          || StringUtils.isBlank(rmsServiceCode)) {
        throw new AuthException("missing required authentication parameters (rmsApplicationName , rmsSign)");
      }
      //判斷systemTag是否有效
      if (!this.rmsProperties.getApplication().containsKey(rmsApplicationName)) {
        throw new AuthException("unrecognized systemTag:" + rmsApplicationName);
      }
      //獲得應用元數據
      ApplicationMeta applicationMeta = rmsProperties.getApplication().get(rmsApplicationName);
      //獲得secret
      String secret = applicationMeta.getSecret();
      //計算sign
      String sign = Constant.sign(rmsApplicationName, secret);
      //比較sign
      if (!rmsSign.equals(sign)) {
        throw new AuthException("sign Validation failed");
      }
      //判斷是否有調用所有服務的許可權
      if (!applicationMeta.getAll()) {
        //判斷是否禁止調用所有服務許可權
        if (applicationMeta.getDisabled()) {
          throw new PermissionException(rmsApplicationName + " is disabled");
        }
        //判斷是否有調用該服務的許可權
        if (applicationMeta.getPurview().indexOf(rmsServiceCode) == -1) {
          throw new PermissionException("no access to this servoceCode : " + rmsServiceCode);
        }
        //判斷服務元數據是否存在
        if (!rmsProperties.getService().containsKey(rmsServiceCode)) {
          throw new PermissionException("service code not exist");
        }
        //獲得服務元數據
        ServiceMeta serviceMeta = rmsProperties.getService().get(rmsServiceCode);
        //比較url和method的有效性
        if (!serviceMeta.getUri().equals(url) || !serviceMeta.getMethod().equals(method)) {
          throw new PermissionException("url and method verification error");
        }
      }
    }

4.定義RmsConfig類

@Configuration
@ConfigurationProperties(prefix = "org.itkk.rms.config")
@Validated
public class RmsConfig {

  //RMS掃描路徑
  @NotNull
  private String rmsPathPatterns;

  .........
  
}

5.定義RmsConfig類-註冊bean

  @Bean
  @LoadBalanced
  RestTemplate restTemplate(ClientHttpRequestFactory requestFactory) {
    return new RestTemplate(requestFactory);
  }
  
  @Bean
  public RmsAuthHandlerInterceptor rmsAuthHandlerInterceptor() {
    return new RmsAuthHandlerInterceptor();
  }
  
  @Bean
  public WebMvcConfigurer rmsAuthConfigurer() { //NOSONAR
    return new WebMvcConfigurerAdapter() {
      @Override
      public void addInterceptors(InterceptorRegistry registry) {
        String[] rmsPathPatternsArray = rmsPathPatterns.split(",");
        registry.addInterceptor(rmsAuthHandlerInterceptor()).addPathPatterns(rmsPathPatternsArray);
        super.addInterceptors(registry);
      }
    };
  }

6.在properties文件中進行配置

#攔截路徑
org.itkk.rms.config.rmsPathPatterns=/service/**

3.實現Rms類

1.定義rms類

@Component
public class Rms {
  //應用名稱
  @Value("${spring.application.name}")
  private String springApplicationName;
  //restTemplate
  @Autowired
  private RestTemplate restTemplate;
  //配置
  @Autowired
  private RmsProperties rmsProperties;

2.定義rms類-call方法

  public <I, O> ResponseEntity<O> call(String serviceCode, I input, String uriParam,
      ParameterizedTypeReference<O> responseType, Map<String, ?> uriVariables) {
    //客戶端許可權驗證
    verification(serviceCode);
    //構建請求路徑
    String path = getRmsUrl(serviceCode);
    //獲得請求方法
    String method = getRmsMethod(serviceCode);
    //拼裝路徑參數
    if (StringUtils.isNotBlank(uriParam)) {
      path += uriParam;
    }
    //構建請求頭
    HttpHeaders httpHeaders = buildSystemTagHeaders(serviceCode);
    //構建請求消息體
    HttpEntity<I> requestEntity = new HttpEntity<>(input, httpHeaders);
    //請求並且返回
    return restTemplate.exchange(path, HttpMethod.resolve(method), requestEntity, responseType,
        uriVariables != null ? uriVariables : new HashMap<String, String>());
  }

3.定義rms類-其他方法

  //構建請求頭
  private HttpHeaders buildSystemTagHeaders(String serviceCode) {
    String secret = rmsProperties.getApplication().get(springApplicationName).getSecret();
    HttpHeaders headers = new HttpHeaders();
    headers.add(Constant.HEADER_RMS_APPLICATION_NAME_CODE, springApplicationName);
    headers.add(Constant.HEADER_RMS_SIGN_CODE, Constant.sign(springApplicationName, secret));
    headers.add(Constant.HEADER_SERVICE_CODE_CODE, serviceCode);
    return headers;
  }
  //客戶端驗證
  private void verification(String serviceCode) {
    ApplicationMeta applicationMeta = rmsProperties.getApplication().get(springApplicationName);
    if (!applicationMeta.getAll()) {
      if (applicationMeta.getDisabled()) {
        throw new PermissionException(springApplicationName + " is disabled");
      }
      if (applicationMeta.getPurview().indexOf(serviceCode) == -1) {
        throw new PermissionException("no access to this servoceCode : " + serviceCode);
      }
    }
  }
  //獲得請求方法
  private String getRmsMethod(String serviceCode) {
    return rmsProperties.getService().get(serviceCode).getMethod();
  }
  //構造url
  private String getRmsUrl(String serviceCode) {
    //獲取服務元數據
    ServiceMeta serviceMeta = rmsProperties.getService().get(serviceCode);
    //構建請求路徑
    StringBuilder url =
        new StringBuilder(serviceMeta.getIsHttps() ? Constant.HTTPS : Constant.HTTP);
    url.append(rmsProperties.getApplication().get(serviceMeta.getOwner()).getServiceId());
    url.append(serviceMeta.getUri());
    return url.toString();
  }
  //計算sign
  public static String sign(String rmsApplicationName, String secret) {
    final String split = "_";
    StringBuilder sb = new StringBuilder();
    sb.append(rmsApplicationName).append(split).append(secret).append(split)
        .append(new SimpleDateFormat(DATA_FORMAT).format(new Date()));
    return DigestUtils.md5Hex(sb.toString());
  }

3.客戶端調用

//獲得文件信息
ResponseEntity<RestResponse<FileInfo>> fileInfo = rms.call("FILE_4", fileParam, null,
              new ParameterizedTypeReference<RestResponse<FileInfo>>() {
              }, null);

代碼倉庫 (博客配套代碼)

結束

這樣,規範了遠程服務的調用,只關心介面編號和介面的入參和出參,能夠增加溝通效率,並且也有了輕量級的服務治理機制,服務間的調用更可控,到最後,配置文件一拉出來一清二楚.


想獲得最快更新,請關註公眾號

想獲得最快更新,請關註公眾號


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 11 which.min(), which.max()和which() which(x, arr.ind = FALSE, useNames = TRUE) x 是一個向量或者數組,可以是NA,但會省略掉,相當於FALSE。 arr.ind = FALSE 當x是數組時,是否返回數組索引(arr.i ...
  • 一、掌握靜態方法和屬性 靜態方法和屬性用於描述某一類對象群體的特征,而不是單個對象的特征。Java中大量應用了靜態方法和屬性,這是一個通常的技巧。但是這種技巧在很多語言中不被頻繁地使用。理解靜態方法和屬性對於理解類與對象的關係是十分有幫助的,在大量的Java規範中,靜態方法和屬性被頻繁使用。因此學習 ...
  • 如下圖, 檢查過maven的pom中已經把servlet相關的jar包依賴進來,但還是有這個錯誤。 需要為eclipse添加目標環境(targeted runtimes) 右擊項目--> properties -->選擇targeted runtimes , 可以添加一個環境 刷新或者編譯這個項目, ...
  • 一道編程題如下: 實例化三個線程,一個線程列印a,一個列印b,一個列印c,三個線程同時執行,要求列印出6個連著的abc 題目分析: 通過題意我們可以得出,本題需要我們使用三個線程,三個線程分別會列印6次字元,關鍵是如何保證順序一定是abc...呢。所以此題需要同步機制來解決問題! 令列印字元A的線程 ...
  • apply() apply(m,dimcode,f,fargs) m 是一個矩陣。 dimcode是維度編號,取1則為對行應用函數,取2則為對列運用函數。 f是函數 fargs是f的可選參數集 ...
  • 第一種方法:按快捷鍵 Win+R (run),然後運行框中輸入cmd。 第二種方法:開始菜單--》運行--》然後運行框中輸入cmd。 第三種方法:在附件當中,找命令行選項即可。 ...
  • bin (binary)二進位 ,JDK當中所有的可以執行的二進位應用程式都放在其中。其中都是*.exe文件,表示可以直接執行程式。 javac.exe和java.exe雙擊後為什麼一閃而過,沒了?因為可執行程式分為兩種:一種是圖形化界面,可以雙擊操作,而另一種是命令行的方式執行的,所以就閃沒了。 ...
  • 我在本地用Jackson可以復現這個問題了。 這是我復現的代碼,我每次產生一個隨機的integer作為map的key,然後用objectMapper反序列化。然後我運行我的另外一個PrintStringTable的類,可以看到每次產生的Integer都會進入Constant Pool中如果我把構造O ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...