网关
网关:就是网络的关口,负责请求的路由、转发、身份校验。

网关路由
- 新建网关模块gateway
- 引入相关依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
<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 gateway: routes: - id: item-service uri: lb://item-service predicates: - Path=/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路由断言
文档: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();
|
- 在微服务中定义拦截器,保存网关发过来的用户信息到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(); } }
|
@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) { Long userId = UserContext.getUser(); if (userId != null) { template.header("user-info", userId.toString()); } } }; } }
|
注意:DefaultFeignConfig配置类想要生效,必须在发起请求所在的服务
(交易服务)的启动类上添加:
@EnableFeignClients(basePackages = "com.hmall.api.client", defaultConfiguration = DefaultFeignConfig.class)
|
Nacos配置管理
【存在问题
】:
- 微服务重复配置过多,维护成本高。
- 业务配置经常变动,每次修改都要重启服务
- 网关路由配置写死,如果变更都要重启网关

配置共享
1. 添加共享配置

2. 拉取共享配置
基于NacosConfig拉取共享配置代替微服务的本地配置

- 引入依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency>
<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 { String configInfo = nacosConfigManager.getConfigService() .getConfigAndSignListener(dataId, group, 5000, new Listener() { @Override public Executor getExecutor() { return null; }
@Override public void receiveConfigInfo(String configInfo) { updateConfigInfo(configInfo); } }); updateConfigInfo(configInfo); }
public void updateConfigInfo(String configInfo) { List<RouteDefinition> routes = JSONUtil.toList(configInfo, RouteDefinition.class); for (String routeId : routeIds) { writer.delete(Mono.just(routeId)).subscribe(); } routeIds.clear(); for (RouteDefinition route : routes) { writer.save(Mono.just(route)).subscribe(); routeIds.add(route.getId()); } } }
|
- 在nacos中添加动态路由

【注】:为了方便解析从nacos读取到的路由配置,推荐使用json格式的路由配置。