Spring Gateway集成nacos实现动态路由配置

  • A+
所属分类:体育平台

Spring Gateway集成nacos实现动态路由配置

本文主要介绍Spring Gateway通过集成nacos实现路由动态配置,达到不重启API网关实现动态暴露内部微服务接口的目的。主要流程如下:

一、创建Maven项目test-gateway, pom文件如下:

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.test.gateway</groupId>
	<artifactId>test-gateway</artifactId>
	<version>1.0.0</version>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
		<spring.cloud.version>Hoxton.SR8</spring.cloud.version>
		<alibaba.cloud.version>2.2.3.RELEASE</alibaba.cloud.version>
		<fastjson.version>1.2.73</fastjson.version>
		<spring.boot.version>2.3.2.RELEASE</spring.boot.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-gateway</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
		</dependency>
		<!-- sentinel提供的gataway适配器 -->
		<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
		</dependency>
		<!--sentinel依赖包 -->
		<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba.csp</groupId>
			<artifactId>sentinel-datasource-nacos</artifactId>
		</dependency>
		<!-- 对外暴露 Spring Boot 监控指标 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>${fastjson.version}</version>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring.cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
			<dependency>
				<groupId>com.alibaba.cloud</groupId>
				<artifactId>spring-cloud-alibaba-dependencies</artifactId>
				<version>${alibaba.cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
			<!-- Import dependency management from Spring Boot -->
			<dependency>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-dependencies</artifactId>
				<version>${spring.boot.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<finalName>test-gateway</finalName>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
			<plugin>
				<groupId>org.codehaus.mojo</groupId>
				<artifactId>versions-maven-plugin</artifactId>
				<configuration>
					<generateBackupPoms>false</generateBackupPoms>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>8</source>
					<target>8</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

二、创建启动类Apllication.java,内容如下:

package com.test.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.ComponentScan;

@EnableDiscoveryClient
@SpringBootApplication
@ComponentScan(basePackages = { 
      "com.test.gateway"})
public class GatewayApplication {
      public static void main(String[] args) {
        System.setProperty("csp.sentinel.app.type", "1");
        SpringApplication.run(GatewayApplication.class, args);
      }
}

三、创建网关调用nacos配置类GatewayConfig.java

package com.test.gateway.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

@Configuration
public class GatewayConfig {
      public static final long DEFAULT_TIMEOUT = 30000;

      public static String NACOS_SERVER_ADDR;

      public static String NACOS_NAMESPACE;

      public static String NACOS_ROUTE_DATA_ID;

      public static String NACOS_ROUTE_GROUP;

      @Value("${spring.cloud.nacos.discovery.server-addr}")
      public void setNacosServerAddr(String nacosServerAddr) {
        NACOS_SERVER_ADDR = nacosServerAddr;
      }

      @Value("${spring.cloud.nacos.discovery.namespace}")
      public void setNacosNamespace(String nacosNamespace) {
        NACOS_NAMESPACE = nacosNamespace;
      }

      @Value("${nacos.gateway.route.config.data-id}")
      public void setNacosRouteDataId(String nacosRouteDataId) {
        NACOS_ROUTE_DATA_ID = nacosRouteDataId;
      }

      @Value("${nacos.gateway.route.config.group}")
      public void setNacosRouteGroup(String nacosRouteGroup) {
        NACOS_ROUTE_GROUP = nacosRouteGroup;
      }
}

四、创建动态路由管理服务

1、创建动态路管理类DynamicRouteServiceImpl.java

       package com.test.gateway.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;

import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;


/**
 * 动态更新路由网关service
 * 1)实现一个Spring提供的事宜推送接口ApplicationEventPublisherAware
 * 2)提供动态路由的基础方式,可通过获取bean操作该类的方式。该类提供新增路由、更新路由、删除路由,然后实现公布的功效。
 */
@Slf4j
@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {
      @Autowired
        private RouteDefinitionWriter routeDefinitionWriter;

        /**
         * 公布事宜
         */
        @Autowired
        private ApplicationEventPublisher publisher;

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

        /**
         * 删除路由
         * @param id
         * @return
         */
        public String delete(String id) {
            try {
                log.info("gateway delete route id {}",id);
                this.routeDefinitionWriter.delete(Mono.just(id));
                return "delete success";
            } catch (Exception e) {
                return "delete fail";
            }
        }
        /**
         * 更新路由
         * @param definition
         * @return
         */
        public String update(RouteDefinition definition) {
            try {
                log.info("gateway update route {}",definition);
                this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
            } catch (Exception e) {
                return "update fail,not find route  routeId: "+definition.getId();
            }
            try {
                routeDefinitionWriter.save(Mono.just(definition)).subscribe();
                this.publisher.publishEvent(new RefreshRoutesEvent(this));
                return "success";
            } catch (Exception e) {
                return "update route fail";
            }
        }

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

2、创建通过nacos对路由动态管理类DynamicRouteServiceImplByNacos.java

package com.test.gateway.config;

import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;

import lombok.extern.slf4j.Slf4j;

/**
 *
 * 通过nacos下发动态路由设置,监听Nacos中gateway-route设置
 *
 */
@Component
@Slf4j
@DependsOn({ "gatewayConfig" }) // 依赖于gatewayConfig bean
public class DynamicRouteServiceImplByNacos {
        @Autowired
        private DynamicRouteServiceImpl dynamicRouteService;

        private ConfigService configService;

        @PostConstruct
        public void init() {
          log.info("gateway route init...");
          try {
            configService = initConfigService();
            if (configService == null) {
              log.warn("initConfigService fail");
              return;
            }
            String configInfo = configService.getConfig(GatewayConfig.NACOS_ROUTE_DATA_ID,
                GatewayConfig.NACOS_ROUTE_GROUP, GatewayConfig.DEFAULT_TIMEOUT);
            log.info("获取网关当前设置:rn{}", configInfo);
            List<RouteDefinition> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
            for (RouteDefinition definition : definitionList) {
              log.info("update route : {}", definition.toString());
              dynamicRouteService.add(definition);
            }
          } catch (Exception e) {
            log.error("初始化网关路由时发生错误", e);
          }
          dynamicRouteByNacosListener(GatewayConfig.NACOS_ROUTE_DATA_ID, GatewayConfig.NACOS_ROUTE_GROUP);
        }

        /**
         * 监听Nacos下发的动态路由设置
         * 
         * @param dataId
         * @param group
         */
        public void dynamicRouteByNacosListener(String dataId, String group) {
          try {
            configService.addListener(dataId, group, new Listener() {
              @Override
              public void receiveConfigInfo(String configInfo) {
                log.info("举行网关更新:nr{}", configInfo);
                List<RouteDefinition> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
                for (RouteDefinition definition : definitionList) {
                  log.info("update route : {}", definition.toString());
                  dynamicRouteService.update(definition);
                }
              }

              @Override
              public Executor getExecutor() {
                log.info("getExecutornr");
                return null;
              }
            });
          } catch (NacosException e) {
            log.error("从nacos吸收动态路由设置失足!!!", e);
          }
        }

        /**
         * 初始化网关路由 nacos config
         * 
         * @return
         */
        private ConfigService initConfigService() {
          try {
            Properties properties = new Properties();
            properties.setProperty("serverAddr", GatewayConfig.NACOS_SERVER_ADDR);
            properties.setProperty("namespace", GatewayConfig.NACOS_NAMESPACE);
            return configService = NacosFactory.createConfigService(properties);
          } catch (Exception e) {
            log.error("初始化网关路由时发生错误", e);
            return null;
          }
        }
      }

五、创建网关服务配置文件bootstrap.yml

server:
    port: 80

spring:
    profiles:
       active: dev
    application: 
       name: test-gateway
    cloud:
       nacos: 
         config:
            namespace: ${spring.profiles.active}
            server-addr: http://127.0.0.1:8848
            extension-configs[0]: 
                    data-id: test_gateway_commons.yml
                    group: DEFAULT_GROUP
                    refresh: true

#nacos dynamic router config
nacos: 
    gateway: 
         route: 
            config: 
               data-id: gateway_dynamic_router
               group: DEFAULT_GROUP

1、test_gateway_commons.yml配置文件内容下:

#sentinel 相关配置
spring: 
    cloud: 
       sentinel: 
           transport: 
                dashboard: http://127.0.0.1.2:8080
                port: 8719
           scg: 
                fallback: 
                   mode: response
                   response-status: 455
                   response-body: error!
       nacos: 
          discovery: 
              namespace: dev
              server-addr: 127.0.0.1:8848

management: 
      endpoints: 
            web: 
              exposure: 
                  include: '*'

2、JSON路由配置文件gateway_dynamic_router的内容如下:

[{
    "id": "order-router",
    "order": 0,
    "predicates": [{
        "args": {
            "pattern": "/orders/**"
        },
        "name": "Path"
    }],
    "uri": "lb://order-service"
},{
    "id": "user-router",
    "order": 2,
    "predicates": [{
        "args": {
            "pattern": "/users/**"
        },
        "name": "Path"
    }],
    "filters":[
      {
          "name":"StripPrefix",
          "args":{"_genkey_0":1}
      }
    ],
    "uri": "lb://test-user-service"
}]

通过以上步骤就实现了Spring Gateway集成nacos实现路由动态配置的功能。以后只要修改gateway_dynamic_router 文件就可以实现服务的微服务的接口暴露和下线功能。

demo代码地址如下:https://gitee.com/sharepublicly/test-gateway

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: