使用Spring Cloud Gateway網關設計開放平臺

什麼是開放平臺

企業須要把本身的服務經過接口的形式對外提供,提供接口的平臺稱之爲開放平臺。好比支付寶開放平臺淘寶開放平臺java

調用接口那一方通常稱之爲ISV,獨立軟體開發商(independent software vendor),開放平臺這一方稱之爲ISP,網絡服務提供者(Internet Service Provider)。一般狀況下須要爲ISV分配一個appKey和appSecret,能夠簡單的理解爲用戶名密碼,有了這個才能正常調用開放平臺接口。react

爲了保證請求參數的合法性,客戶端須要生成一個簽名串,而後開放平臺須要校驗這個簽名串。ISV能夠經過appKey和AppSecret來生成簽名串,這樣就能保證客戶端請求是合法的,服務端須要校驗簽名串是否合法,appKey是否合法,這裏開放平臺會提供一套簽名算法,常見的有:支付寶開放平臺簽名算法git

如何設計一個開放平臺

開放平臺的一個重要部分就是鑑權,鑑權功能和具體的業務無關,能夠單獨拿出來作,若是是單體應用的話能夠把這部分操做寫在一個Controller中。若是是微服務體系的話把鑑權部分放在網關是一個不錯的選擇,由於網關是一個統一入口,在入口處作好鑑權,後續的微服務不須要再作鑑權處理了,只需實現本身的業務邏輯便可。github

在Spring Cloud微服務體系當中,充當網關的角色常見有兩個,一個是Zuul,另外一個是Spring Cloud Gateway。Zuul本質是一個Servlet,IO模型是BIO,阻塞式,而Spring Cloud Gateway基於Netty開發的,IO模型是AIO,也就是異步IO,在處理高併發請求場景下,Spring Cloud Gateway具備明顯優點。二者各有優缺點,Zuul優勢是架構簡單,擴展起來比較方便,缺點是在處理高併發請求下稍顯力不從心,Spring Cloud Gateway優勢是高性能,能夠處理高併發請求,缺點是架構複雜,須要瞭解異步編程、Netty、React等框架基本原理,調試起來比較困難。web

本篇拿Spring Cloud Gateway來演示如何設計一個簡單的開放平臺。算法

首先簡單介紹下Spring Cloud Gateway的基本功能,做爲網關,首要的功能是路由功能,簡單理解就是將一個A請求變成B請求,相似於Nginx的反向代理。另外一個功能請求過濾,Spring Cloud Gateway容許開發者實現自定義過濾器,用來處理當前請求。spring

Spring Cloud Gateway的路由配置有兩種,一種是寫在配置文件裏面,一種使用代碼實現(Java DSL)。編程

  • 使用配置文件
server:  
  port: 8090  

spring:  
  cloud:  
    gateway:  
      routes:  
        - id: host_route  
          uri: https://www.baidu.com/  
          pedicates:  
            - Path=/

上面這個配置,在瀏覽器訪問http://localhost:8090,頁面會出現百度首頁。瀏覽器

另外一種使用Java代碼形式:網絡

@Bean  
public RouteLocator customRouteLocator(RouteLocatorBuilder routeBuilder) {  
    return routeBuilder.routes()  
            .route("host_route", r ->  
                    r.path("/")  
                     .uri("https://www.baidu.com/")  
            )  
            .build();  
}

其效果跟配置文件是同樣的,若是路由配置固定不變可寫在配置文件中,若是涉及到動態改變路由配置,就必須寫在Java代碼中了,由於代碼更靈活。

假設咱們咱們的開放平臺接口地址爲:http://open.xx.com,該接口提供一個參數method,表示接口名,經過接口名來決定具體請求哪一個微服務。好比訪問http://open.xx.com/?method=goods.get,轉發到商品微服務http://192.168.1.1:8080/getGoods,以下圖所示:

網關請求

因而可知,在網關須要配置一套路由:

spring:
  cloud:
    gateway:
      routes:
        - id: getGoods
          uri: http://192.168.1.1:8080
          predicates:
            - Path=/getGoods
        - id: getOrder
          uri: http://192.168.1.2:8080
          predicates:
            - Path=/getOrder

接下來須要在網關中作幾件事情:

  1. 鑑權,鑑權失敗返回錯誤碼
  2. 鑑權經過,進行路由轉發

這些事情均可以在全局過濾器中執行,過濾器代碼以下:

package com.example.gateway.filter;

import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

import java.util.HashMap;
import java.util.Map;

/**
 網關入口過濾器
 * @author tanghc
 */
@Component
public class IndexFilter implements WebFilter, Ordered {

    private static Map<String, String> methodPathMap = new HashMap<>(16);
    // 存放接口名對應的path
    static {
        methodPathMap.put("goods.get", "/getGoods");
        methodPathMap.put("order.get", "/getOrder");
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        // 獲取請求參數
        Map<String, String> query = exchange.getRequest().getQueryParams().toSingleValueMap();
        // 鑑權
        this.check(query);

        String method = query.get("method");
        String path = methodPathMap.get(method);
        if (path != null) {
            // 複製一個新的request
            ServerHttpRequest newRequest = exchange.getRequest()
                    .mutate()
                    // == 關鍵在這裏,從新定義轉發的path
                    .path(path)
                    .build();
            // 複製一個新的exchange,request用新的
            ServerWebExchange newExchange = exchange
                    .mutate()
                    .request(newRequest)
                    .build();
            // 向後轉發新的exchange
            return chain.filter(newExchange);
        }
        return chain.filter(exchange);
    }

    /**
     * 鑑權
     * @param query
     */
    private void check(Map<String, String> query) {

    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }

}

這裏經過一個map來存放method和path的對應關係,而後經過重寫request中的path來實現轉發功能。在瀏覽器請求http://localhost:8090/?method=order.get,轉發到http://192.168.1.1:8080/getGoods

至此開放平臺的一個基本功能就實現了,不過依然存在幾個問題:

  1. 路由配置是寫死的,沒法動態加載
  2. 接口名對應的Path是寫死的,沒法動態變動
  3. 如何獲取POST請求參數
  4. 如何獲取上傳文件請求參數

針對第一個問題,解決辦法是使用Java代碼(Java DSL)來配置路由,具體的思路是讓微服務來維護路由關係,而後網關在啓動完畢後去各個微服務端拉取路由配置,保存到本地。

第二個問題,能夠使用動態配置,如Spring Cloud Config或阿波羅配置來動態改變。

以上涉及到的全部問題在SOP中已經所有實現。

相關文章
相關標籤/搜索