共计 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;}
}
个人博客地址
正文完
发表至: springboot
2020-07-02