Springcloud Gateway 路由管理


Spring Cloud Gateway 是 Spring Cloud 的一個全新項目,該項目是基於 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技術開發的網關,它旨在為微服務架構提供一種簡單有效的統一的 API 路由管理方式。Spring Cloud Gateway 作為 Spring Cloud 生態系統中的網關,目標是替代 Netflix Zuul,其不僅提供統一的路由方式,並且基於 Filter 鏈的方式提供了網關基本的功能,例如:安全,監控/指標,和限流。

相應的入門demo網上很多,我們這邊一筆帶過


 

1.引入pom依賴

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

 

2.application.yml 配置文件 

server:
  servlet:
    context-path: /
  port: 18889
spring:
  application:
    name: client-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: false #表明gateway開啟服務注冊和發現的功能,並且spring cloud gateway自動根據服務發現為每一個服務創建了一個router,# 這個router將以服務名開頭的請求路徑轉發到對應的服務
          lower-case-service-id: true #將請求路徑上的服務名配置為小寫(因為服務注冊的時候,向注冊中心注冊時將服務名轉成大寫的了,比如以/service-hi/*的請求路徑被路由轉發到服務名為service-hi的服務上
      routes:
      - id: test-id
        uri: lb://client-manage
        order: -1
        predicates:
        - Path=/api2/**
        filters:
        - StripPrefix=1
  • id:我們自定義的路由 ID,保持唯一
  • uri:目標服務地址
  • predicates:路由條件,Predicate 接受一個輸入參數,返回一個布爾值結果。該接口包含多種默認方法來將 Predicate 組合成其他復雜的邏輯(比如:與,或,非)。
  • filters:過濾規則,

 上面那段路由配置表示所有已包含 /api2/ 的url都會被路由到 client-manage服務,StripPrefix=1表示路由時會去除/api2/。

 

我們也可以使用api的方式配置

@Bean
    public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
        // @formatter:off
        return builder.routes ()
                .route (r -> r.path ("/api2/**")
                        .filters (f -> f.stripPrefix (1))
                        .uri ("lb://client-manage")
                        .order (0)
                        .id ("client-manage")
                )
                .build ();
    }

 

這兩種配置效果是一致。

 

路由擴展

基於上面兩種配置,我們的網關已經具有了路由的功能了。但是這里還不夠工程化。設想下,現在我有上百個路由信息,配置文件或者api的形式去配置必然會導致可讀性的缺失。同時我還想實現不停機的增加路由。這里就引入了動態增加路由的概念。翻看Gateway的代碼,發現Gateway代碼本身就支持動態增加路由,相關代碼在 org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint#save

 

入參的 RouteDefinition 是對路由的封裝,與上文的配置文件一一相對應。最終實際調用的是  RouteDefinitionWriter的save方法。

 

基於上述代碼,我們要做以下幾件事情

1.定義外部存儲文件中的RouteDefinition的數據結構

2.啟動時自動讀取路由信息寫入內存中。

 


單個路由的添加

@PostMapping(value = "addRoute")
    @ResponseBody
    public String  addRoute() throws URISyntaxException{

            RouteDefinition routeDefinition=new RouteDefinition();
            routeDefinition.setId("test-id");
            List<PredicateDefinition> predicates=new ArrayList<>();
            PredicateDefinition definition=new PredicateDefinition();
            //注意name
            definition.setName("Path");
            definition.addArg("pattern","/api2/**");
            predicates.add(definition);
            routeDefinition.setPredicates(predicates);

            List<FilterDefinition> filters =new ArrayList<>();
            FilterDefinition filterDefinition = new FilterDefinition();
            //注意name
            filterDefinition.setName("StripPrefix");
            filterDefinition.addArg("parts","1");
            filters.add(filterDefinition);
            routeDefinition.setFilters(filters);

            URI uri = new URI("lb://client-manage");
            routeDefinition.setUri(uri);
            routeDefinition.setOrder(0);
            String save = routeService.add(routeDefinition);
            System.out.println(save);
            return "";
    }

 

為確保功能的實現,我們先寫死一個配置嘗試用這種方式配置路由。這里注意兩點

PredicateDefinition配置

PredicateDefinition的name代表每一個工廠類,只能從以下選擇

 

 addArgs的key是相應方法參數名稱

public GatewayFilterSpec stripPrefix(int parts) {
        return filter(getBean(StripPrefixGatewayFilterFactory.class)
                .apply(c -> c.setParts(parts)));
    }

 FilterDefinition配置

FilterDefinition和PredicateDefinition類似,name從以下選擇

 

配置文件讀取

單個配置生效后我們開始外部文件的形式去配置,這里為了便捷我依然從項目配置文件讀取。實際我們可以把配置放到數據庫,緩存。

新建api.properties文件

新增配置

api.methods.api2={"predicateDefinition":[{"predicateValue":"/api2/**","name":"Path","predicateKey":"pattern"}],"id":"test_id","uri":"lb://client-manage","filterDefinition":[{"filterKey":"parts","filterValue":"1","name":"StripPrefix"}],"order":"0"}

 

 定義RouteDefines讀取配置文件

@Configuration
@PropertySource("classpath:api.properties")
@ConfigurationProperties(prefix = "api")
public class RouteDefines {

  public Map<String, String> methods = new HashMap<>();

  public Map<String, String> getMethods() {
    return methods;
  }

  public void setMethods(Map<String, String> methods) {
    this.methods = methods;
  }

}

 

定義InitRouteApplication初始化路由信息寫入內存

package com.hdkj.client.gateway;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.hdkj.client.gateway.configuration.RouteDefines;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.stereotype.Component;

/**
 * @author Xu.Minzhe
 * @version V1.0
 * @package com.hdkj.client.gateway
 * @class: InitRouteApplication.java
 * @description: 初始化路由信息
 * @Date 2019-04-25 10:15
 */
@Component
public class InitRouteApplication implements ApplicationRunner {
        private static final Logger logger = LoggerFactory.getLogger(InitRouteApplication.class);

        @Autowired
        private RouteDefines routeDefines;

        @Autowired
        private DynamicRouteService routeService;

        @Override
        public void run(ApplicationArguments args) throws Exception {
                Map<String, String> methods = routeDefines.getMethods();
                methods.values().stream().forEach(x->{
                        try {
                                System.out.println("配置文件讀取的信息"+x);
                                JSONObject jsonObject = JSONObject.parseObject(x);
                                //組裝RouteDefinition
                                RouteDefinition routeDefinition = getRouteDefinition(jsonObject);
                                //路由信息寫入
                                String save = routeService.add(routeDefinition);
                        } catch (Exception e) {
                                logger.error("[路由初始化] 異常",e);
                        }
                });
        }

        /**
         * 組裝RouteDefinition
         * @param jsonObject
         * @return
         * @throws URISyntaxException
         */
        private RouteDefinition getRouteDefinition(JSONObject jsonObject) throws URISyntaxException {
                RouteDefinition routeDefinition=new RouteDefinition();
                routeDefinition.setId(jsonObject.getString("id"));
                List<PredicateDefinition> predicateList = getPredicateList(jsonObject);
                routeDefinition.setPredicates(predicateList);

                List<FilterDefinition> filterDefinition1 = getFilterDefinition(jsonObject);
                routeDefinition.setFilters(filterDefinition1);

                URI uri = new URI(jsonObject.getString("uri"));
                routeDefinition.setUri(uri);
                routeDefinition.setOrder(jsonObject.getIntValue("order"));
                return routeDefinition;
        }

        /**
         * 解析json 獲得PredicateList
         * @param jsonObject
         * @return
         */
        private List<PredicateDefinition> getPredicateList(JSONObject jsonObject) {
                JSONArray predicateDefinition = jsonObject.getJSONArray("predicateDefinition");
                List<PredicateDefinition> predicates=new ArrayList<>();
                predicateDefinition.stream().forEach(predicate->{
                        JSONObject jsonObject1 = JSONObject.parseObject(predicate.toString());
                        PredicateDefinition definition=new PredicateDefinition();
                        definition.setName(jsonObject1.getString("name"));
                        definition.addArg(jsonObject1.getString("predicateKey"),jsonObject1.getString("predicateValue"));
                        predicates.add(definition);

                });
                return predicates;
        }

        /**
         * 解析json 獲得FilterDefinitionList
         * @param jsonObject
         * @return
         */
        private List<FilterDefinition> getFilterDefinition(JSONObject jsonObject) {
                JSONArray predicateDefinition = jsonObject.getJSONArray("filterDefinition");
                List<FilterDefinition> predicates=new ArrayList<>();
                predicateDefinition.stream().forEach(predicate->{
                        JSONObject jsonObject1 = JSONObject.parseObject(predicate.toString());
                        FilterDefinition definition=new FilterDefinition();
                        definition.setName(jsonObject1.getString("name"));
                        definition.addArg(jsonObject1.getString("filterKey"),jsonObject1.getString("filterValue"));
                        predicates.add(definition);

                });
                return predicates;
        }
}

 

 

這里DynamicRouteService是路由寫入實現類

@Component
public class DynamicRouteService implements ApplicationEventPublisherAware {
        @Autowired
        private RouteDefinitionWriter routeDefinitionWriter;

        private ApplicationEventPublisher publisher;


        /**
         * 增加路由
         * @param definition
         * @return
         */
        public String add(RouteDefinition definition) {
                routeDefinitionWriter.save(Mono.just(definition)).subscribe();
                this.publisher.publishEvent(new RefreshRoutesEvent(this));
                return "success";
        }

        @Override
        public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
                this.publisher = applicationEventPublisher;

        }
}

 

以上便是路由加載的實現。至於動態新增路由,有了以上的代碼,實現也是相當簡單了。這里不再敘述。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM