网关
网关:就是网络的关口,负责请求的路由、转发、身份校验。
网关路由
- 新建网关模块gateway
- 引入相关依赖
<!--网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos discovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
- 写启动类
- 配置路由规则
server:
port: 8080 # 前端请求的端口(网关的端口)
spring:
application:
name: gateway # 服务名称
cloud:
nacos:
server-addr: 192.168.140.101:8848 # nacos地址
# 路由配置
gateway:
routes:
- id: item-service # 路由规则id,自定义,唯一
uri: lb://item-service # 路由目标微服务,lb代表负载均衡协议
predicates: # 路由断言,判断请求是否符合要求,符合则路由到目标
- Path=/items/**, /search/** # 以请求路径做判断,以/items或/search开头则符合
- id: user-service
uri: lb://user-service
predicates:
- Path=/addresses/**, /users/**
- id: trade-service
uri: lb://trade-service
predicates:
- Path=/orders/**
- id: pay-service
uri: lb://pay-service
predicates:
- Path=/pay-orders/**
- id: cart-service
uri: lb://cart-service
predicates:
- Path=/carts/**
路由属性
网关路由对应的Java类型是RouteDefinition,常见的属性有:
- id:路由唯一标识
- uri:路由目标地址
- predicates:路由断言,判断请求是否符合当前路由
- filters:路由过滤器,对请求或响应做特殊处理
predicates路由断言
filters路由过滤器
文档:filters路由过滤器
自定义过滤器
网关过滤器有两种:
- GatewayFilter:路由过滤器,作用于任意指定的路由,默认不生效,要陪知道路由后生效。
- GlobalFilter(常用):全局过滤器,作用范围是所有路由;声明后自动生效。
自定义GlobalFilter
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取请求
HttpHeaders headers = exchange.getRequest().getHeaders();
// 过滤器业务处理(登录校验逻辑...)
if(...) {
// 拦截
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
return chain.filter(exchange); // 放行
}
// 控制过滤器执行顺序
@Override
public int getOrder() {
return 0; // 值越小,优先级越高
}
}
自定义的过滤器需要在NettyRoutingFilter【将请求转发到微服务】这个过滤器之前执行,所以需要再继承一个Ordered接口,来保证我们自定义的过滤器的优先级比NettyRoutingFilter高
网关登录校验
网关传递用户
- 在网关的登录校验过滤器中,从前端发送的请求头里拿到用户信息,并把用户信息放到请求头里,再发给微服务。
ServerWebExchange swe = exchange.mutate()
.request(builder -> builder.header("user-info", userInfo))
.build(); // 传递的时候,需要传递这个返回的新的exchange
- 在微服务中定义拦截器,保存网关发过来的用户信息到ThreadLocal里。
// 定义拦截器
// 因为校验请求头这些操作已经在网关做过了,所以拦截器里边只需要把用户信息保存到ThreadLocal里即可
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取用户信息
String userInfo = request.getHeader("user-info");
// 判断是否获取了用户信息
if(StrUtil.isNotBlank(userInfo)) {
// 存入上下文
UserContext.setUser(Long.valueOf(userInfo));
}
// 放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 清理用户
UserContext.removeUser();
}
}
// 注册拦截器
/**
* DispatcherServlet.class:springmvc的核心api
* 防止网关【没有SpringMvc】也引用这个类
*/
@ConditionalOnClass(DispatcherServlet.class) // 条件注解
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor());
}
}
由于很多微服务,都需要获取用户信息,不可能在这么多微服务里都一个拦截器,太麻烦啦。所以就把拦截器的代码写在common公共模块。
【问题
】:配置类想要生效,需要被Spring扫描包扫描到,但是现在mvc配置类是在common公共模块下,但是是其他微服务使用这个拦截器,无法扫描到这个配置类。
【解决
】:利用SpringBoot自动装配的原理,将定义的配置类放在了META-INF下的spring.factories文件下,这样就能实现自动装
OpenFeign传递用户信息
【分析
】:购物车服务中的请求,不是直接从网关发过来的,而是网关先发给交易服务,再由交易服务通过OpenFeign向购物车服务中发送请求【微服务之间的调用】。
【解决
】:OpenFeign提供了一个拦截器接口,所有由OpenFeign发起的请求都会先调用拦截器处理请求。
public class DefaultFeignConfig {
@Bean
public RequestInterceptor userInfoRequestInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
// 交易服务可以拿到用户信息,它向购物车发请求时,UserContext里会保存用户信息
Long userId = UserContext.getUser();
if (userId != null) {
// 把用户信息放到请求头中
template.header("user-info", userId.toString());
}
}
};
}
}
Nacos配置管理
【存在问题
】:
- 微服务重复配置过多,维护成本高。
- 业务配置经常变动,每次修改都要重启服务
- 网关路由配置写死,如果变更都要重启网关
配置共享
1. 添加共享配置
打开nacos控制台,添加一些共享配置到nacos中,包括:jdbc、日志、swagger、openfeign等配置。
2. 拉取共享配置
基于NacosConfig拉取共享配置代替微服务的本地配置
- 引入依赖
<!--nacos配置管理-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--读取bootstrap文件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
2.新建bootstrap.yaml文件
spring:
application:
name: cart-service # 微服务的名称
profiles:
active: dev
cloud:
nacos:
server-addr: 192.168.140.101:8848
config:
file-extension: yaml
shared-configs:
- data-id: shared-jdbc.yaml
- data-id: shared-log.yaml
- data-id: shared-swagger.yaml
先加载bootstrap配置文件,拉取nacos配置,再进行合并。
配置热更新
当修改配置文件中的配置时,微服务无需重启即可使配置生效。
【条件
】:
- nacos中要有一个与微服务名有关的配置文件。
- 微服务中要以特定方式读取需要热更新的配置属性。
@Data
@Component
@ConfigurationProperties(prefix = "hm.cart")
public class CartProperties {
private Integer maxItems;
}
对应nacos中的配置:
动态路由
要实现动态路由首先要将路由配置保存到nacos中,当nacos中路由配置变更时,推送最新配置到网关,更新网关中的路由信息。
- 拉取配置并添加监听器
- 在
路由表里的内容变更
和项目启动
时,更新路由表
@Component
@RequiredArgsConstructor
public class DynamicRouteLoader {
private final NacosConfigManager nacosConfigManager;
private final RouteDefinitionWriter writer;
private final String dataId = "gateway-routes.json";
private final String group = "DEFAULT_GROUP";
private final Set<String> routeIds = new HashSet<>(); // 保存旧的路由表
@PostConstruct // 在项目一启动的时候执行
public void init() throws NacosException {
// 1. 项目启动,先拉取配置,并添加配置监听器
String configInfo = nacosConfigManager.getConfigService()
.getConfigAndSignListener(dataId, group, 5000, new Listener() {
@Override
public Executor getExecutor() { // 定义线程池
return null;
}
@Override
public void receiveConfigInfo(String configInfo) { // 配置变更时做的事
// 2. 监听到配置变更时,需要去更新路由表
updateConfigInfo(configInfo);
}
});
// 3. 第一次读取到配置,也需要更新路由表
updateConfigInfo(configInfo);
}
// 更新路由表【利用RouteDefinitionWriter来更新路由表】
public void updateConfigInfo(String configInfo) { // configInfo就是配置文件的内容
// 1. 解析配置文件,转为RouteDefinition
List<RouteDefinition> routes = JSONUtil.toList(configInfo, RouteDefinition.class);
// 2. 更新路由表
// 【删】删除旧的路由表
for (String routeId : routeIds) {
writer.delete(Mono.just(routeId)).subscribe();
}
// 清空旧的路由表
routeIds.clear();
for (RouteDefinition route : routes) {
// 【增】新增新的路由表
writer.save(Mono.just(route)).subscribe();
// 记录路由id,便于下次更新时删除
routeIds.add(route.getId());
}
}
}
- 在nacos中添加动态路由
标签:网关,配置,配置管理,Nacos,nacos,public,路由,路由表 From: https://blog.csdn.net/qq_57882997/article/details/145009706【注】:为了方便解析从nacos读取到的路由配置,推荐使用json格式的路由配置。