springcloud系列之gateway动态网关

2次阅读

共计 7692 个字符,预计需要花费 20 分钟才能阅读完成。

springcloud 系列之 gateway

简介

spirngcloud-gateway 是 Spring 全家桶的网关组件。基于 webflux 异步非阻塞、性能好、详细介绍请绕道 gateway 官网。本文基于 nacos 服务注册发现中心,实现网关的配置、动态网关的修改和刷新。
打开 RouteDefinition 类,gateway 的路由是有 id、PredicateDefinition(断言)、filters、uri(目标地址)、metadata 和 order 组成的
项目地址:https://github.com/wotrd/naco…

@Validated
public class RouteDefinition {
    private String id;
    @NotEmpty
    @Valid
    private List<PredicateDefinition> predicates = new ArrayList<>();
    @Valid
    private List<FilterDefinition> filters = new ArrayList<>();
    @NotNull
    private URI uri;
    private Map<String, Object> metadata = new HashMap<>();
    private int order = 0;

使用方式

1、添加依赖

 gateway 依赖
  <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-gateway</artifactId>
     <version>Hoxton.SR6</version>
  </dependency>
注册和配置中心依赖
 <dependency>
     <groupId>com.alibaba.cloud</groupId>
     <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
     <version>2.2.1.RELEASE</version>
 </dependency>
 <dependency>
     <groupId>com.alibaba.cloud</groupId>
     <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
     <version>2.2.1.RELEASE</version>
 </dependency>

2、配置文件配置路由

1、这里,我们使用 nacos 配置中心,先创建 bootstrap.properties 启动配置文件加载远程配置。

#nacos 配置中心地址
spring.cloud.nacos.config.server-addr=x.x.x.x:8848
#配置文件分组
spring.cloud.nacos.config.group=GATEWAY_GROUP
#配置文件后缀,使用 yml 文件需要配置
spring.cloud.nacos.config.file-extension=yml

2、在注册中心创建 gate-way-service-dev.yml 文件

spring:
  cloud:
    gateway:
      routes:
#        - id: after_route
#          uri: https://www.baidu.com/
#          predicates:
#            - After=2020-06-29T06:06:06+08:00[Asia/Shanghai]
        - id: before_route
          uri: https://www.ailijie.top
          predicates:
            - After=2020-06-20T06:06:06+08:00[Asia/Shanghai]
        - id: myRoute
          uri: lb://feign-service
          predicates:
            - Path=/feign-service/**

3、代码配置路由

创建 RouteConfig 配置类

@Configuration
public class RouteConfig {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        Collection<GatewayFilter> fils = null;
        return builder.routes()
                .route("path_route", predicateSpec ->
                        predicateSpec.path("/feign-service")
                                .uri("http://www.ailijie.top")
                                .filter(new GlobalGateFilter())
                )
                .build();}
}

4、配置动态网关路由

使用 yml 配置文件方式,每次都要修改配置文件,风险较大。代码写死拓展性更查,因此,我们需要简单的动态配置,使用 redis+mysql 实现。每次修改网关需要刷新路由发送 spring 事件,分布式部署需要注意主机刷新,默认 30 秒刷新一次。实体类可以根据表结构去创建。

1、添加数据库和 redis 依赖

 <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
 </dependency>
 <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.3</version>
 </dependency>
 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
 </dependency>

2、只贴核心,全部可以去 git 仓库 https://github.com/wotrd/naco… 中找。接下来创建 RedisRouteDefinitionRepository 类,动态刷新路由。

@Repository
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {

    @Autowired
    private RouteService routeService;

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {log.info("[gateway] change gate start");
        List<RouteDefinition> definitions = getDefinitions();
        log.info("[gateway] change gate success:{}", JSONObject.toJSONString(definitions));
        return Flux.fromIterable(definitions);

    }

    /**
     * 获取网关配置
     *
     * @return
     */
    private List<RouteDefinition> getDefinitions() {List<Route> routes = routeService.routeList();
        List<RouteDefinition> routeDefinitions = new ArrayList<>(routes.size());
        routes.stream().forEach(route -> {RouteDefinition routeDefinition = new RouteDefinition();
            routeDefinition.setId(route.getId());
            routeDefinition.setOrder(route.getOrder());
            routeDefinition.setMetadata(getMetadata(route.getMetadatas()));
            List<FilterDefinition> filters = getFilters(route.getFilters());
            routeDefinition.setFilters(filters);
            List<PredicateDefinition> predicates = getPredicates(route.getPredicates());
            routeDefinition.setPredicates(predicates);
            routeDefinition.setUri(getUri(route.getTargetUrl()));
            routeDefinitions.add(routeDefinition);
        });

        return routeDefinitions;
    }

3、创建 RouteService 路由操作类

    /**
     * 获取路由列表
     *
     * @return
     */
    public List<Route> routeList() {
        try {String routeStr = template.opsForValue().get(GATEWAY_ROUTES);
            if (!StringUtils.isEmpty(routeStr)) {return JSONObject.parseArray(routeStr, Route.class);
            }
        } catch (Exception e) {log.error("[gateway] get route from redis error:", e);
        }
        List<Route> routes = routeMapper.selectAll();
        if (!CollectionUtils.isEmpty(routes)) {routes.stream().forEach(route -> {route.setMetadatas(getRouteMetadatas(route.getId()));
                route.setFilters(getRouteFilters(route.getId()));
                route.setPredicates(getRoutePredicates(route.getId()));
            });
        }
        try {template.opsForValue().set(GATEWAY_ROUTES, JSONObject.toJSONString(routes));
        } catch (Exception e) {log.error("[gateway] set route to redis error:", e);
        }
        return routes;
    }

    /**
     * 刷新路由
     */
    public void refresh() {publisher.publishEvent(new RefreshRoutesEvent(this));

    }

4、创建表结构

-- auto-generated definition
drop table if exists route;
create table route
(id          varchar(64)      not null comment '主键'
        primary key,
    app_id      int              not null comment '应用 ID',
    app_name    varchar(64)      not null comment '应用名字',
    target_url  varchar(128)     null comment '目标地址',
    `order`     int    default 0 not null comment '执行顺序',
    create_user varchar(64)      not null comment '创建时间',
    update_user varchar(64)      null,
    create_time datetime         null,
    update_time datetime         null,
    deleted     int(1) default 0 not null comment '是否删除(1 是 0 否)'
)
    comment '网关主表';

create index idx_app_id
    on route (app_id);
-- auto-generated definition
drop table if exists route_filter;
create table route_filter
(
    id          bigint auto_increment comment '主键 ID'
        primary key,
    route_id    varchar(64) not null,
    filter_name varchar(64) not null comment '过滤器名字',
    deleted     int(1)      null comment '是否删除'
)
    comment '网关过滤器';

create index idx_route_id
    on route_filter (route_id);

-- auto-generated definition
drop table if exists route_filter_args;
create table route_filter_args
(
    id        bigint auto_increment
        primary key,
    filter_id bigint      not null,
    `key`     varchar(64) not null,
    value     varchar(64) null,
    deleted   int(1)      null
);

create index idx_filter_id
    on route_filter_args (filter_id);
    
-- auto-generated definition
drop table if exists route_metadata;
create table route_metadata
(
    id          bigint auto_increment comment '主键 ID'
        primary key,
    route_id    varchar(64)      not null,
    `key`       varchar(64)      not null comment '元数据 key',
    value       varchar(128)     null comment '元数据值',
    create_user varchar(64)      null,
    update_user varchar(64)      null,
    create_time datetime         null,
    update_time datetime         null,
    deleted     int(1) default 0 not null comment '是否删除(1 是 0 否)'
)
    comment '网关元数据表';

create index idx_route_id
    on route_metadata (route_id);

-- auto-generated definition
drop table if exists route_predicate;
create table route_predicate
(
    id             bigint auto_increment
        primary key,
    route_id       varchar(64)      not null,
    predicate_name varchar(64)      not null comment '断言名字',
    update_time    datetime         null,
    create_user    varchar(64)      null,
    update_user    varchar(64)      null,
    create_time    datetime         null,
    deleted        int(1) default 0 not null
)
    comment '网关断言';

create index idx_route_id
    on route_predicate (route_id);

-- auto-generated definition
drop table if exists route_predicate_args;
create table route_predicate_args
(
    id           bigint auto_increment
        primary key,
    predicate_id bigint           not null,
    `key`        varchar(64)      null,
    value        varchar(128)     null,
    update_user  varchar(64)      null,
    create_user  varchar(64)      null,
    update_time  datetime         null,
    create_time  datetime         null,
    deleted      int(1) default 0 not null
);

create index idx_predicate_id
    on route_predicate_args (predicate_id);

测试方式和代码写死,配置文件一样

5、网关过滤器(有全局过滤和路由过滤)

全局路由实现 GlobalFilter 接口,不需要配置,GatewayFilter 需要在配置文件中配置

@Component
public class GlobalGateFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpResponse response = exchange.getResponse();
        // 从请求中获取 token 参数
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        // 如果为空,那么将返回 401
        if (token == null || token.isEmpty()) {
            // 响应消息内容对象
            JSONObject message = new JSONObject();
            // 响应状态
            message.put("code", -1);
            // 响应内容
            message.put("msg", "缺少凭证");
            // 转换响应消息内容对象为字节
            byte[] bits = message.toJSONString().getBytes(StandardCharsets.UTF_8);
            DataBuffer buffer = response.bufferFactory().wrap(bits);
            // 设置响应对象状态码 401
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            // 设置响应对象内容并且指定编码,否则在浏览器中会中文乱码
            response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");
            // 返回响应对象
            return response.writeWith(Mono.just(buffer));
        }

        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {return -100;}
}

个人博客地址

正文完
 0