Spring Cloud Alibaba 集成 Gateway 实现动态路由功能

1 摘要

在一般情况下,业务相对固定,使用静态路由,直接在项目配置中写死即可应对。但是在业务拓展和业务变更较多较大的情况下,则可以通过动态路由来更好管理服务。本文将介绍基于 Spring Cloud Alibaba 2.2 集成 Gateway 实现动态路由功能。

Spring Cloud Alibaba 2.2 简易集成 Gateway:

Spring Cloud Alibaba 集成网关Gateway

Gateway 全局过滤功能:

Spring Cloud Alibaba 集成网关 Gateway 全局过滤功能

2 核心 Maven 依赖

./cloud-alibaba-gateway-filter/pom.xml
    <dependencies>
        <!-- cloud alibaba -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
        </dependency>
        <!-- cloud feign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>${spring-cloud-openfeign.version}</version>
        </dependency>
        <!-- cloud gateway -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <version>${spring-cloud-gateway.version}</version>
        </dependency>
        
        <!-- hutool -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.version}</version>
        </dependency>
        <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
            <scope>runtime</scope>
        </dependency>
        <!-- Mybatis Plus(include Mybatis) -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

        
        <!-- 省略其他依赖 -->

    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

其中 ${spring-cloud-alibaba.version} 的版本为 2.2.3.RELEASE , ${spring-cloud-gateway.version} 的版本为 2.2.5.RELEASE${spring-cloud-openfeign.version} 的版本为 2.2.5.RELEASE

${hutool.version} 的版本为5.7.8, ${mysql.version} 的版本为 8.0.27, ${mybatis-plus.version} 的版本为 3.4.3.4

注意事项: Spring Cloud Alibaba 2.2.3.RELEASE 版本支持的 Spring Boot 版本为 2.3.1.RELEASE ,建议在搭建项目时要保持版本的一致性,Spring Boot 版本过高或过低都可能导致不兼容问题

Spring Cloud Gateway 不能和 Spring Boot Web 一起打包

3 名词释义

Gateway 网关的路由类:

org.springframework.cloud.gateway.route.Route

路由对象(Route) 组成属性:

public class Route implements Ordered {
    private final String id;
    private final URI uri;
    private final int order;
    private final AsyncPredicate<ServerWebExchange> predicate;
    private final List<GatewayFilter> gatewayFilters;
    private final Map<String, Object> metadata;
    
    // ... ...
    
}    

id : 路由 id,不可重复

uri: 路由地址,指通过该路由的请求会转发到当前 uri 路径上去

order: 拦截器的执行顺序,数值越小,优先级越高, 取值范围为: -21474836482147483647

predicate : 断言,指符合当前条件的请求会走当前的路由, Spring Cloud Gateway 自带 10 种断言模式,最常用的是 Path route Predicate FactoryHost Route Predicate Factory, 更多断言资料参考官方文档:

Route Predicate Factories

gatewayFilters: 过滤器,拦截器,当符合指定断言的请求进入当前路由之后,会进入当前路由的拦截器中,一个路由可以绑定多个拦截器,根据优先级依次执行。Spring Cloud Gateway 定义了大概 30 种拦截器,最常用的有 AddRequestHeader GatewayFilterFactoryPrefixPath GatewayFilterFactory 。更多拦截器资料参考官方文档:

GatewayFilter Factories

metadata: 元数据,用于定义一些额外的参数

4 Gateway 动态路由原理

Gateway 提供了一系列的方法来实现路由的管理功能,从而可以实现动态路由,在 GatewayControllerEndpoint类中已经定义了路由的增删查等功能

org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint

借助 Swagger 可以更加清晰地看到 Gateway 预定义的接口:

spring-cloud-gateway-dynamic-route-api
可以根据名称猜测出大致的功能含义

5 数据库表

为了方便管理,可以将路由信息保存到本地。

./doc/sql/gateway-database-create.sql
/*==============================================================*/
/* DBMS name:      MySQL 5.0                                    */
/* Created on:     2021/10/21 15:41:38                          */
/*==============================================================*/


drop table if exists gateway_route;

/*==============================================================*/
/* Table: gateway_route                                         */
/*==============================================================*/
create table gateway_route
(
   id                   bigint unsigned not null auto_increment comment '数据库id',
   route_id             varchar(32) comment '路由id',
   uri                  varchar(128) comment '请求地址',
   predicates           varchar(512) comment '断言',
   filters              varchar(512) comment '拦截器',
   metadata             varchar(128) comment '附加参数',
   filter_order         int comment '执行顺序,数值越小优先级越高',
   create_date          bigint unsigned comment '创建时间',
   update_date          bigint unsigned comment '更新时间',
   primary key (id)
);

alter table gateway_route comment '网关路由';

6 核心代码

6.1 配置信息
./cloud-alibaba-gateway-filter/src/main/resources/application.yml
## config

## server
server:
  port: 8612

## spring
spring:
  application:
    name: cloud-alibaba-gateway-filter
  ## datasource
  datasource:
    url: "jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=utf8\
      &useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2b8\
      &useSSL=true&allowMultiQueries=true&autoReconnect=true&nullCatalogMeansCurrent=true\
      &nullCatalogMeansCurrent=true"
    username: root
    password: "root"
    hikari:
      driver-class-name: com.mysql.cj.jdbc.Driver
  ## cloud
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
#    gateway:
#      discovery:
#        locator:
#          enabled: true

## endpoint
management:
  endpoints:
    web:
      exposure:
        include: "*"

6.2 路由实体类
./cloud-alibaba-gateway-filter/src/main/java/com/ljq/demo/springboot/alibaba/gateway/filter/model/RouteEntity.java
package com.ljq.demo.springboot.alibaba.gateway.filter.model;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;

/**
 * @Description: 路由实体类
 * @Author: junqiang.lu
 * @Date: 2021/10/20
 */
@Data
@TableName(value = "gateway_route")
public class RouteEntity implements Serializable {

    private static final long serialVersionUID = -7838086812254411423L;

    /**
     * 数据库 ID
     */
    @TableId(type = IdType.NONE)
    private Long id;
    /**
     * 路由 id
     */
    private String routeId;
    /**
     * 路由转发地址
     */
    private String uri;
    /**
     * 断言
     */
    private String predicates;
    /**
     * 过滤器
     */
    private String filters;
    /**
     * 其他参数
     */
    private String metadata;
    /**
     * 过滤器等级,执行顺序,数值越小优先级越高
     */
    private Integer filterOrder;
    /**
     * 创建时间
     */
    private Long createDate;
    /**
     * 更新时间
     */
    private Long updateDate;

}
6.3 本地路由数据库持久层(DAO/Mapper)
./cloud-alibaba-gateway-filter/src/main/java/com/ljq/demo/springboot/alibaba/gateway/filter/dao/RouteMapper.java
package com.ljq.demo.springboot.alibaba.gateway.filter.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ljq.demo.springboot.alibaba.gateway.filter.model.RouteEntity;
import org.springframework.stereotype.Repository;

/**
 * @Description: 路由数据库持久层
 * @Author: junqiang.lu
 * @Date: 2021/10/21
 */
@Repository
public interface RouteMapper extends BaseMapper<RouteEntity> {

}
6.4 操作 Gateway 路由的 Repository
./cloud-alibaba-gateway-filter/src/main/java/com/ljq/demo/springboot/alibaba/gateway/filter/repository/DbRouteRepository.java
package com.ljq.demo.springboot.alibaba.gateway.filter.repository;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ljq.demo.springboot.alibaba.gateway.filter.common.util.RouteConvert;
import com.ljq.demo.springboot.alibaba.gateway.filter.dao.RouteMapper;
import com.ljq.demo.springboot.alibaba.gateway.filter.model.RouteEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;

/**
 * @Description: 数据库路由持久层
 * @Author: junqiang.lu
 * @Date: 2021/10/19
 */
@Slf4j
@Component
public class DbRouteRepository implements RouteDefinitionRepository {

    @Autowired
    private RouteMapper routeMapper;

    /**
     * 获取路由信息
     *
     * @return
     */
    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        List<RouteEntity> routeEntityList = routeMapper.selectList(Wrappers.emptyWrapper());
        if (CollUtil.isEmpty(routeEntityList)) {
            log.info("数据库内没有定义路由");
            return Flux.empty();
        }
        List<RouteDefinition> routeDefinitionList = new ArrayList<>();
        routeEntityList.stream().forEach(routeEntity -> {
            routeDefinitionList.add(RouteConvert.toRouteDefinition(routeEntity));
        });
        log.info("json 数据库路由列表: {}", JSONUtil.toJsonStr(routeDefinitionList));
        return  Flux.fromIterable(routeDefinitionList);
    }

    /**
     * 新增路由
     *
     * @param route
     * @return
     */
    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return route.flatMap(routeDefinition -> {
            routeMapper.insert(RouteConvert.toRouteEntity(routeDefinition));
            return Mono.empty();
        });
    }

    /**
     * 删除路由
     *
     * @param routeId
     * @return
     */
    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        return routeId.flatMap(id -> {
            routeMapper.delete(Wrappers.lambdaQuery(new RouteEntity()).eq(RouteEntity::getRouteId, id));
            return Mono.empty();
        });
    }
}

在默认情况下,应用程序启动时会查询路由列表,即执行 getRouteDefinitions() 方法。在和 Spring Cloud Alibaba 集成后,程序启动之后,会每间隔 30 秒查询路由一次。为避免给数据库造成压力,可以考虑添加缓存。

6.4 路由管理业务层 (Service)
./cloud-alibaba-gateway-filter/src/main/java/com/ljq/demo/springboot/alibaba/gateway/filter/service/DynamicRouteService.java
package com.ljq.demo.springboot.alibaba.gateway.filter.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.ljq.demo.springboot.alibaba.gateway.filter.model.RouteDeleteParam;
import com.ljq.demo.springboot.alibaba.gateway.filter.model.RouteEntity;
import org.springframework.cloud.gateway.route.RouteDefinition;

import java.util.List;

/**
 * @Description: 动态路由业务接口
 * @Author: junqiang.lu
 * @Date: 2021/10/19
 */
public interface DynamicRouteService extends IService<RouteEntity> {


    /**
     * 查询启用的路由列表
     *
     * @return
     */
    List<RouteDefinition> listActive();

    /**
     * 新增路由
     *
     * @param routeDefinition
     */
    void add(RouteDefinition routeDefinition);

    /**
     * 修改路由
     *
     * @param routeDefinition
     */
    void update(RouteDefinition routeDefinition);

    /**
     * 删除路由
     *
     * @param routeDeleteParam
     */
    void delete(RouteDeleteParam routeDeleteParam);

}
./cloud-alibaba-gateway-filter/src/main/java/com/ljq/demo/springboot/alibaba/gateway/filter/service/impl/DynamicRouteServiceImpl.java
package com.ljq.demo.springboot.alibaba.gateway.filter.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ljq.demo.springboot.alibaba.gateway.filter.dao.RouteMapper;
import com.ljq.demo.springboot.alibaba.gateway.filter.model.RouteDeleteParam;
import com.ljq.demo.springboot.alibaba.gateway.filter.model.RouteEntity;
import com.ljq.demo.springboot.alibaba.gateway.filter.repository.DbRouteRepository;
import com.ljq.demo.springboot.alibaba.gateway.filter.service.DynamicRouteService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * @Description: 动态路由业务实现类
 * @Author: junqiang.lu
 * @Date: 2021/10/19
 */
@Slf4j
@Service
public class DynamicRouteServiceImpl extends ServiceImpl<RouteMapper, RouteEntity> implements DynamicRouteService, ApplicationEventPublisherAware {

    @Autowired
    private DbRouteRepository dbRouteRepository;

    private ApplicationEventPublisher applicationEventPublisher;


    /**
     * 查询启用的路由列表
     *
     * @return
     */
    @Override
    public List<RouteDefinition> listActive() {
        Flux<RouteDefinition> routeDefinitionFlux = dbRouteRepository.getRouteDefinitions();
        if (Objects.isNull(routeDefinitionFlux)) {
            return Collections.emptyList();
        }
        List<RouteDefinition> routeDefinitionList = new ArrayList<>();
        routeDefinitionFlux.collectList().subscribe(routeDefinitionList::addAll);
        return routeDefinitionList;
    }

    /**
     * 新增路由
     *
     * @param routeDefinition
     */
    @Override
    public void add(RouteDefinition routeDefinition) {
        dbRouteRepository.save(Mono.just(routeDefinition)).subscribe();
        applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
    }

    /**
     * 修改路由
     *
     * @param routeDefinition
     */
    @Override
    public void update(RouteDefinition routeDefinition) {
        dbRouteRepository.delete(Mono.just(routeDefinition.getId())).subscribe();
        dbRouteRepository.save(Mono.just(routeDefinition)).subscribe();
        applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
    }

    /**
     * 删除路由
     *
     * @param routeDeleteParam
     */
    @Override
    public void delete(RouteDeleteParam routeDeleteParam) {
        dbRouteRepository.delete(Mono.just(routeDeleteParam.getRouteId())).subscribe();
        applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
    }


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

}
6.5 路由管理控制层
./cloud-alibaba-gateway-filter/src/main/java/com/ljq/demo/springboot/alibaba/gateway/filter/controller/DynamicRouteController.java
package com.ljq.demo.springboot.alibaba.gateway.filter.controller;

import com.ljq.demo.springboot.alibaba.gateway.filter.common.api.ApiResult;
import com.ljq.demo.springboot.alibaba.gateway.filter.model.RouteDeleteParam;
import com.ljq.demo.springboot.alibaba.gateway.filter.service.DynamicRouteService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * @Description: 动态路由控制层
 * @Author: junqiang.lu
 * @Date: 2021/10/19
 */
@Slf4j
@RestController
@RequestMapping("/api/gateway/route")
public class DynamicRouteController {

    @Autowired
    private DynamicRouteService routeService;

    /**
     * 查询启用的路由列表
     *
     * @return
     */
    @GetMapping(value = "/list/active", produces = {MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<ApiResult<List<RouteDefinition>>> listActive() {
        log.info("/list/active");
        return ResponseEntity.ok(ApiResult.success(routeService.listActive()));
    }

    /**
     * 新增路由
     *
     * @param routeDefinition
     * @return
     */
    @PostMapping(value = "/add", produces = {MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<ApiResult<RouteDefinition>> add(@RequestBody @Validated RouteDefinition routeDefinition) {
        log.info("/add,新增路由参数: {}", routeDefinition);
        routeService.add(routeDefinition);
        return ResponseEntity.ok(ApiResult.success(routeDefinition));
    }

    /**
     * 更新路由
     *
     * @param routeDefinition
     * @return
     */
    @PutMapping(value = "/update", produces = {MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<ApiResult<RouteDefinition>> update(@RequestBody @Validated RouteDefinition routeDefinition) {
        log.info("/update,新增路由参数: {}", routeDefinition);
        routeService.update(routeDefinition);
        return ResponseEntity.ok(ApiResult.success(routeDefinition));
    }

    /**
     * 删除路由
     *
     * @param routeDeleteParam
     * @return
     */
    @DeleteMapping(value = "/delete", produces = {MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<ApiResult<RouteDeleteParam>> delete(@RequestBody RouteDeleteParam routeDeleteParam) {
        log.info("/delete,删除路由参数: {}", routeDeleteParam);
        routeService.delete(routeDeleteParam);
        return ResponseEntity.ok(ApiResult.success(routeDeleteParam));
    }



}
6.6 其他相关类

本地路由实体类与 Gateway 路由定义类转换类:

./cloud-alibaba-gateway-filter/src/main/java/com/ljq/demo/springboot/alibaba/gateway/filter/common/util/RouteConvert.java
package com.ljq.demo.springboot.alibaba.gateway.filter.common.util;

import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.ljq.demo.springboot.alibaba.gateway.filter.model.RouteEntity;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;

import java.net.URI;
import java.util.Map;

/**
 * @Description: 路由器转换工具类
 * @Author: junqiang.lu
 * @Date: 2021/10/21
 */
public class RouteConvert {

    private RouteConvert(){
    }

    /**
     * 转换为 RouteDefinition
     *
     * @param routeEntity
     * @return
     */
    public static RouteDefinition toRouteDefinition(RouteEntity routeEntity) {
        RouteDefinition routeDefinition = new RouteDefinition();
        routeDefinition.setId(routeEntity.getRouteId());
        routeDefinition.setOrder(routeEntity.getFilterOrder());
        routeDefinition.setUri(URI.create(routeEntity.getUri()));
        JSONArray predicateArray = new JSONArray(JSONUtil.toJsonStr(routeEntity.getPredicates()));
        if (!predicateArray.isEmpty()) {
            routeDefinition.setPredicates(predicateArray.toList(PredicateDefinition.class));
        }
        JSONArray filtersArray = new JSONArray(JSONUtil.toJsonStr(routeEntity.getFilters()));
        if (!filtersArray.isEmpty()) {
            routeDefinition.setFilters(filtersArray.toList(FilterDefinition.class));
        }
        routeDefinition.setMetadata(JSONUtil.toBean(routeEntity.getMetadata(), Map.class));
        return routeDefinition;
    }

    /**
     * 转换为路由数据库实体类
     *
     * @param routeDefinition
     * @return
     */
    public static RouteEntity toRouteEntity(RouteDefinition routeDefinition) {
        RouteEntity routeEntity = new RouteEntity();
        routeEntity.setRouteId(routeDefinition.getId());
        routeEntity.setUri(routeDefinition.getUri().toString());
        routeEntity.setPredicates(JSONUtil.toJsonStr(routeDefinition.getPredicates()));
        routeEntity.setFilters(JSONUtil.toJsonStr(routeDefinition.getFilters()));
        routeEntity.setMetadata(JSONUtil.toJsonStr(routeDefinition.getMetadata()));
        routeEntity.setFilterOrder(routeDefinition.getOrder());
        return routeEntity;
    }


}

删除路由请求参数:

./cloud-alibaba-gateway-filter/src/main/java/com/ljq/demo/springboot/alibaba/gateway/filter/model/RouteDeleteParam.java
package com.ljq.demo.springboot.alibaba.gateway.filter.model;

import lombok.Data;

import javax.validation.constraints.NotBlank;
import java.io.Serializable;

/**
 * @Description: 删除路由
 * @Author: junqiang.lu
 * @Date: 2021/10/22
 */
@Data
public class RouteDeleteParam implements Serializable {

    private static final long serialVersionUID = -2214111091023615696L;

    /**
     * 路由 ID
     */
    @NotBlank(message = "路由ID不能为空")
    private String routeId;

}

用于路由测试的控制层:

./cloud-alibaba-gateway-filter/src/main/java/com/ljq/demo/springboot/alibaba/gateway/filter/controller/DemoController.java
./package com.ljq.demo.springboot.alibaba.gateway.filter.controller;

import com.ljq.demo.springboot.alibaba.gateway.filter.common.api.ApiResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Description: 测试接口控制层
 * @Author: junqiang.lu
 * @Date: 2021/10/22
 */
@Slf4j
@RestController
@RequestMapping(value = "/api/shop/user")
public class DemoController {

    /**
     * 查询列表
     *
     * @return
     */
    @GetMapping(value = "/list")
    public ResponseEntity<?> list(){
        log.info("/api/user/list");
        return ResponseEntity.ok(ApiResult.success("请求成功啦!!!"));
    }


}

用于测试的 SpringBoot 启动类:

主要是通过不同的端口实现多个服务

./cloud-alibaba-gateway-filter/src/main/java/com/ljq/demo/springboot/alibaba/gateway/filter/CloudAlibabaGatewayFilter2Application.java
package com.ljq.demo.springboot.alibaba.gateway.filter;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * @author junqiang.lu
 */
@EnableDiscoveryClient
@SpringBootApplication(scanBasePackages = {"com.ljq.demo.springboot.*"})
@MapperScan(basePackages = {"com.ljq.demo.springboot.alibaba.gateway.filter.dao"})
public class CloudAlibabaGatewayFilter2Application {

    public static void main(String[] args) {
        System.setProperty("server.port", "8613");
        SpringApplication.run(CloudAlibabaGatewayFilter2Application.class, args);
    }

}

接口返回结果:

./cloud-alibaba-gateway-filter/src/main/java/com/ljq/demo/springboot/alibaba/gateway/filter/common/api/ApiResult.java

7 测试

7.1 新增路由

请求地址:

http://127.0.0.1:8612/api/gateway/route/add

请求方式: POST

请求参数:

{
    "filters": [
        {
            "args": {
                "prefix":"/api"
            },
            "name": "PrefixPath"
        }
    ],
    "id": "demoRouter701",
    "metadata": {},
    "order": 0,
    "predicates": [
        {
            "args": {
                "pattern": "/shop/user/**"
            },
            "name": "Path"
        }
    ],
    "uri": "http://127.0.0.1:8613"
}

关于参数定义:

断言以及拦截器中的参数名一定要与 Gateway 官方的定义保持一致,具体可参考官方文档:

过滤器定义官方文档:
https://cloud.spring.io/spring-cloud-gateway/reference/html/#gatewayfilter-factories

断言定义官方文档:
https://cloud.spring.io/spring-cloud-gateway/reference/html/#gateway-request-predicates-factories

添加断言与路由时会对参数进行校验,具体类如下:

加载过滤器(filters):

org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#loadGatewayFilters

spring-cloud-gateway-route-filter-name-1

spring-cloud-gateway-route-filter-name-2

加载断言(predicates):

org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#loadGatewayFilters

spring-cloud-gateway-route-predicate-definition-name

7.2 查询路由列表

请求地址:

http://127.0.0.1:8612/api/gateway/route/list/active

请求方式: GET

请求结果:

{
    "key": "api.response.success",
    "message": "SUCCESS",
    "data": [
        {
            "id": "demoRouter701",
            "predicates": [
                {
                    "name": "Path",
                    "args": {
                        "pattern": "/shop/user/**"
                    }
                }
            ],
            "filters": [
                {
                    "name": "PrefixPath",
                    "args": {
                        "prefix": "/api"
                    }
                }
            ],
            "uri": "http://127.0.0.1:8613",
            "metadata": {},
            "order": 0
        }
    ],
    "extraDataMap": null,
    "timestamp": 1635143729231
}
7.3 修改路由

请求路径:

http://127.0.0.1:8612/api/gateway/route/update

请求方式: PUT

请求参数: 格式同新增

7.4 删除路由

请求路径:

http://127.0.0.1:8612/api/gateway/route/delete

请求方式: DELETE

请求参数:

{
    "routeId": "demoRouter26"
}
7.5 测试匹配路由规则的接口

根据上边的路由规则,路径中以 /shop/user/ 开头的接口就会走路由

接口地址:

http://127.0.0.1:8612/shop/user/list

请求方式: GET

请求参数: 无

返回结果:

{
    "key": "api.response.success",
    "message": "SUCCESS",
    "data": "请求成功啦!!!",
    "extraDataMap": null,
    "timestamp": 1635143656374
}

当前的返回结果为端口为 8613 的服务返回的,表明路由已经生效

7.6 测试不匹配路由规则的接口

接口地址:

http://127.0.0.1:8612/user/info

请求方式: GET

请求参数: 无

返回结果:

{
    "timestamp": "2021-10-25T06:39:35.241+00:00",
    "path": "/user/info",
    "status": 404,
    "error": "Not Found",
    "message": null,
    "requestId": "d9a4b20c-18"
}

当前结果为 SpringBoot 返回的默认 404 错误,没有匹配路由规则就不会通过网关,到达对应的服务

7.7 测试微服务代理

在配置信息中,以下配置是表明 Gateway 将自动代理当前注册中心下所有的服务

## spring
spring:
  ## cloud
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true

将这一部分注释,则 Gateway 不再自动代理服务

当添加动态路由之后,Gateway 将遵循新的路由规则,也不再自动代理注册中心的服务

8 推荐参考资料

Spring Cloud Gateway 官方文档

详解SpringCloud-gateway动态路由两种方式,以及路由加载过程

spring cloud 2.x版本 Gateway动态路由教程

Convert Flux into List, Map – Reactor

Reactor 3快速上手——响应式Spring的道法术器


9 Github 源码

Gtihub 源码地址 : https://github.com/Flying9001/springBootDemo

个人公众号:404Code,分享半个互联网人的技术与思考,感兴趣的可以关注.
404Code

  • 5
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Nacos是一个分布式的服务注册和发现系统。Spring Cloud Gateway是开源的网关,可以实现动态路由。结合Nacos和Spring Cloud Gateway可以实现动态路由功能。 下面是通过Nacos实现Spring Cloud Gateway动态路由的步骤: 1. 添加Nacos组件。在pom.xml文件中添加以下依赖: ``` <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> ``` 2. 实现Spring Cloud Gateway注册到Nacos的功能。 在application.yml文件中添加以下配置: ``` spring: application: name: gateway-service cloud: gateway: discovery: locator: enabled: true lower-case-service-id: true ``` 其中,将enabled设置为true,表示启用Spring Cloud Gateway的服务发现功能;将lower-case-service-id设置为true,表示服务名全部转换为小写。 3. 实现动态路由。 在Nacos中创建config和route两个配置。config配置用来存放动态路由信息,route配置用来存放每个服务的路由信息。 在route配置中添加以下配置: ``` spring: cloud: gateway: routes: - id: user-service uri: loadbalancer://user-service predicates: - Path=/users ``` 其中,id表示服务名,uri表示服务的负载均衡地址,predicates表示路由谓语,可以是Path、Query等。 在config配置中添加以下配置: ``` spring: cloud: gateway: discovery: locator: enabled: true lower-case-service-id: true routes: - id: user-service predicates: - Path=/api/users/** filters: - StripPrefix=1 uri: lb://user-service metadata: version: 1.0 ``` 其中,id表示服务名,predicates表示路由谓语,filters表示过滤器,StripPrefix表示去掉前缀的过滤器,uri表示服务的负载均衡地址,metadata表示元数据信息。 通过以上配置,就可以实现Nacos和Spring Cloud Gateway动态路由功能

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值