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

文章目录

      • 1 摘要
      • 2 核心 Maven 依赖
      • 3 名词释义
      • 4 Gateway 动态路由原理
      • 5 数据库表
      • 6 核心代码
        • 6.1 配置信息
        • 6.2 路由实体类
        • 6.3 本地路由数据库持久层(DAO/Mapper)
        • 6.4 操作 Gateway 路由的 Repository
        • 6.4 路由管理业务层 (Service)
        • 6.5 路由管理控制层
        • 6.6 其他相关类
      • 7 测试
        • 7.1 新增路由
        • 7.2 查询路由列表
        • 7.3 修改路由
        • 7.4 删除路由
        • 7.5 测试匹配路由规则的接口
        • 7.6 测试不匹配路由规则的接口
        • 7.7 测试微服务代理
      • 8 推荐参考资料
      • 9 Github 源码

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>
        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
            <version>${spring-cloud-alibaba.version}version>
        dependency>
        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-openfeignartifactId>
            <version>${spring-cloud-openfeign.version}version>
        dependency>
        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-gatewayartifactId>
            <version>${spring-cloud-gateway.version}version>
        dependency>
        
        
        <dependency>
            <groupId>cn.hutoolgroupId>
            <artifactId>hutool-allartifactId>
            <version>${hutool.version}version>
        dependency>
        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>${mysql.version}version>
            <scope>runtimescope>
        dependency>
        
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-boot-starterartifactId>
            <version>${mybatis-plus.version}version>
        dependency>

        
        

    dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-alibaba-dependenciesartifactId>
                <version>${spring-cloud-alibaba.version}version>
                <type>pomtype>
                <scope>importscope>
            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 Alibaba 集成 Gateway 实现动态路由功能_第1张图片
可以根据名称猜测出大致的功能含义

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 Alibaba 集成 Gateway 实现动态路由功能_第2张图片

Spring Cloud Alibaba 集成 Gateway 实现动态路由功能_第3张图片

加载断言(predicates):

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

Spring Cloud Alibaba 集成 Gateway 实现动态路由功能_第4张图片

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

你可能感兴趣的:(Java,spring,cloud,gateway,java)