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;
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>
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/**
创建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(); } }
使用yml配置文件方式,每次都要修改配置文件,风险较大。代码写死拓展性更查,因此,我们需要简单的动态配置,使用redis+mysql实现。每次修改网关需要刷新路由发送spring事件,分布式部署需要注意主机刷新,默认30秒刷新一次。实体类可以根据表结构去创建。
<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; }
/** * 获取路由列表 * * @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)); }
-- 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);
测试方式和代码写死,配置文件一样
全局路由实现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; } }