springcloud系列之gateway

简介

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

@Validatedpublic 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配置类

@Configurationpublic 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类,动态刷新路由。

@Repositorypublic 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 definitiondrop 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 definitiondrop 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 definitiondrop 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 definitiondrop 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 definitiondrop 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 definitiondrop 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需要在配置文件中配置

@Componentpublic 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;    }}

个人博客地址