一、网关路由
网关就是网络的关口。数据在网络间传输,从一个网络传输到另一网络时就需要经过网关来做数据的路由和转发以及数据安全的校验。
路由是网关的核心功能之一,决定如何将客户端请求映射到后端服务。
1、快速入门
创建新模块,引入网关依赖,编写启动类,配置路由规则
依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>hmall</artifactId><groupId>com.heima</groupId><version>1.0.0</version></parent><modelVersion>4.0.0</modelVersion><artifactId>hm-gateway</artifactId><properties><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target></properties><dependencies><!--common--><dependency><groupId>com.heima</groupId><artifactId>hm-common</artifactId><version>1.0.0</version></dependency><!--网关--><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></dependencies><build><finalName>${project.artifactId}</finalName><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
新建yml/yaml文件配置路由
server:port: 8080
spring:application:name: gatewaycloud:nacos:server-addr: 192.168.150.101:8848gateway:routes:- id: item # 路由规则id,自定义,唯一uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表(微服务名,从yaml文件里找的)predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务(请求路径,看controller)- Path=/items/**,/search/** # 这里是以请求路径作为判断规则(有多个路径逗号分割,或者下一行重新写 - Path=/search/**)- id: carturi: lb://cart-servicepredicates:- Path=/carts/**- id: useruri: lb://user-servicepredicates:- Path=/users/**,/addresses/**- id: tradeuri: lb://trade-servicepredicates:- Path=/orders/**- id: payuri: lb://pay-servicepredicates:- Path=/pay-orders/**
路由包含四个属性:
id
:路由的唯一标示predicates
:路由断言,其实就是匹配条件filters
:路由过滤器,对请求或响应做特殊处理。uri
:路由目标地址,lb://
代表负载均衡,从注册中心获取目标微服务的实例列表,并且负载均衡选择一个访问。
Spring中提供了12种基本的RoutePredicateFactory实现:
路由断言
名称 | 说明 | 示例 |
---|---|---|
After | 是某个时间点后的请求 | - After=2037-01-20T17:42:47.789-07:00[America/Denver] |
Before | 是某个时间点之前的请求 | - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |
Between | 是某两个时间点之前的请求 | - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver] |
Cookie | 请求必须包含某些cookie | - Cookie=chocolate, ch.p |
Header | 请求必须包含某些header | - Header=X-Request-Id, \d+ |
Host | 请求必须是访问某个host(域名) | - Host=**.somehost.org,**.anotherhost.org |
Method | 请求方式必须是指定方式 | - Method=GET,POST |
Path | 请求路径必须符合指定规则 | - Path=/red/{segment},/blue/** |
Query | 请求参数必须包含指定参数 | - Query=name, Jack或者- Query=name |
RemoteAddr | 请求者的ip必须是指定范围 | - RemoteAddr=192.168.1.1/24 |
weight | 权重处理 |
2、路由过滤
网关中提供了33种路由过滤器,每种过滤器都有独特的作用。
routes:- id: item # 路由规则id,自定义,唯一uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务- Path=/items/**,/search/** # 这里是以请求路径作为判断规则(有多个路径逗号分割,或者下一行重新写 - Path=/search/**)filters:- AddResponseHeader=truth, anyone
如果与routes同一级设置 default-filters,就是对所有路由都生效:
default-filters:- AddResponseHeader=truth, anyone //所有路由都生效
创建启动类:
@SpringBootApplication
public class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);}
}
二、网关登录校验
1、实现思路
单体架构时我们只需要完成一次用户登录、身份校验,就可以在所有业务中获取到用户信息。而微服务拆分后,每个微服务都独立部署,不再共享数据。也就意味着每个微服务都需要做登录校验,这显然不可取。
既然网关是所有微服务的入口,一切请求都需要先经过网关。我们完全可以把登录校验的工作放到网关去做,这样之前说的问题就解决了。
在网关内自定义过滤器,编写登录校验逻辑,并且将过滤器执行顺序定义到NettyRoutingFilter之前,
2、自定义过滤器
网管过滤器有两种,分别是:
- GatewayFilter:路由过滤器,作用于任意指定的路由;默认不生效,要配置到路由后生效。
- GlobalFilter:全局过滤器,作用范围是所有路由;声明后自动生效。
两种过滤器的过滤方法签名完全一样:
/*** 处理请求并将其传递给下一个过滤器* @param exchange 当前请求的上下文,其中包含request、response等各种数据* @param chain 过滤器链,基于它向下传递请求* @return 根据返回值标记当前请求是否被完成或拦截,chain.filter(exchange)就放行了。*/
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
GlobalFilter定义步骤:
- 新建包filters,新建类MyGlobalFilter。
- 添加@Component注解。
- 实现GlobaFilter接口以及其中的filter方法。
- 进行逻辑代码编写
- 实现Ordered接口以及其中的方法。(用来定义优先级)
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//TODO 模拟登录校验逻辑ServerHttpRequest request = exchange.getRequest();HttpHeaders headers = request.getHeaders();System.out.println("header =" + headers);return chain.filter(exchange);}@Overridepublic int getOrder() {return 0;}
}
3、实现登录校验
需求:在网关中基于过滤器实现登录校验功能
定义步骤与 上面GlobalFilter定义步骤一致,具体代码如下:
@Component
@RequiredArgsConstructor
public class AuthGlobalFilter implements GlobalFilter, Ordered {private final JwtTool jwtTool;private final AuthProperties authProperties;private AntPathMatcher antPathMatcher = new AntPathMatcher(); //是Spring 框架提供的路径匹配工具类。@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//1、获取requestServerHttpRequest request = exchange.getRequest();//2、判断是否需要做登录拦截if (isExclude(request.getPath().toString())) {//放行return chain.filter(exchange);}//3、获取tokenString token = null;List<String> headers = request.getHeaders().get("authorization");if (headers != null && !headers.isEmpty()) {token = headers.get(0);}Long userId = null;//4、校验并解析tokentry {userId = jwtTool.parseToken(token);} catch (Exception e) {//拦截,设置响应状态码,401// 获取响应对象ServerHttpResponse response = exchange.getResponse();// 设置响应状态码为401(未授权)response.setStatusCode(HttpStatus.UNAUTHORIZED);// 结束响应,不再继续处理请求return response.setComplete();}//5、TODO 传递用户信息System.out.println("userId=" + userId);//6、放行return chain.filter(exchange);}@Overridepublic int getOrder() {return 0;}private boolean isExclude(String path) {// 获取所有需要排除的路径,来自AuthProperties配置类中的excludePaths属性// AuthProperties通过@ConfigurationProperties注解自动绑定配置文件中hm.auth.exclude-paths的值for (String pathPattern : authProperties.getExcludePaths()) {// 使用AntPathMatcher匹配当前路径是否符合排除路径模式// 如果匹配成功,则说明该路径不需要进行身份验证拦截if (antPathMatcher.match(pathPattern, path)) {return true;}}return false;}
}
4、网关传递用户
①在网关的登录校验过滤器中,把获取得到的用户写入请求头。
修改转发到微服务的请求,需要用到ServerWebExchange类提供的API,示例如下:
修改内容:
//5、传递用户信息String userInfo = userId.toString();ServerWebExchange swe = exchange.mutate().request(builder -> builder.header("user-info", userInfo)) //要写入的请求头,名字可以自己起,要写入的内容.build();//6、放行return chain.filter(swe);
②在hm-common中编写SpringMVC拦截器,获取登录用户。
public class UserInfoInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1、获取用户登录信息String userInfo = request.getHeader("user-info");//2、判断是否获取了用户,如果有,存入ThreadLocalif (StrUtil.isNotBlank(userInfo)) {UserContext.setUser(Long.valueOf(userInfo));}//放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//清理用户UserContext.removeUser();}
}
5、OpenFeign传递用户
OpenFeign中提供了一个拦截器接口,所有由OpenFeign发起的请求都会先调用拦截器处理请求:
public interface RequestInterceptor {/*** Called for every request. * Add data using methods on the supplied {@link RequestTemplate}.*/void apply(RequestTemplate template);
}
@Beanpublic RequestInterceptor userInfoRequestInterceptor() {return new RequestInterceptor() {@Overridepublic void apply(RequestTemplate template) {Long userId = UserContext.getUser();if(userId != null){template.header("user-info",userId.toString());}}};}
微服务登录解决方案
三、配置管理
微服务配置的问题:
- 微服务重复配置过多,维护成本高。
- 业务配置经常变动,每次修改都要重启服务。
- 网关路由配置写死,如果变更要重启网关。
1、配置共享
①将一些公共的配置进行抽取,放到Nacos->配置列表->新建配置中,
${}是可以通过其他配置文件来指明,这些都是不固定的,不能写死,后面的:是添加一个默认值。共享的配置有swagger,log,jdbc
②然后将其中的变量值声明出来:
hm:db:database: hm-cart
③拉取共享配置
基于NacosConfig拉取共享配置代替微服务的本地配置。
SpringCloud在初始化上下文的时候会先读取一个名为bootstrap.yaml
(或者bootstrap.properties
)的文件,如果我们将nacos地址配置到bootstrap.yaml
中,那么在项目引导阶段就可以读取nacos中的配置了。
配置步骤:
1)引入依赖(帮助完成拉取配置的动作, 读取bootstrap文件,创建bootStrap的上下文)
<!--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(包含服务名字,nacos地址,共享文件的名称)
spring:application:name: cart-serviceprofiles:active: dev #开发环境(dev)、测试环境(test)、生产环境(prod)cloud:nacos:server-addr: 192.168.100.128:8848config:file-extension: yamlshared-configs:- data-id: shared-jdbc.yaml- data-id: shared-log.yaml- data-id: shared-swagger.yaml
application.yaml变为:
server:port: 8082
feign:okhttp:enabled: true # 开启OKHttp功能hm:db:database: hm-cartswagger:title: "黑马商城购物车服务接口文档"package: com.hmall.cart.controller# keytool -genkeypair -alias hmall -keyalg RSA -keypass hmall123 -keystore hmall.jks -storepass hmall123
2、配置热更新
配置热更新:当修改配置文件中的配置时,微服务无需重启即可使配置生效。
前提条件:
①nacos中要有一个与微服务名有关的配置文件规则:[服务名]-[spring.active.profile].[后缀名]
②微服务中要以特定的方式读取需要热更新的配置属性 新建config包,创建类并定义属性
@Data
@Component
@ConfigurationProperties(prefix = "hm.cart")
public class CartProperties {private Integer maxAmount;
}
在源代码处进行更改:
private void checkCartsFull(Long userId) {int count = lambdaQuery().eq(Cart::getUserId, userId).count();if (count >= cartProperties.getMaxItems()) {throw new BizIllegalException(StrUtil.format("用户购物车课程不能超过{}", cartProperties.getMaxItems()));}}
3、动态路由
要实现动态路由首先要将路由配置保存到Nacos,当Nacos中的路由配置变更时,推送最新配置到网关,实时更新网关中的路由信息。
要完成的事情:
①监听Nacos配置变更的消息
②当配置变更时,将最新的路由信息更新到网关路由表
//TODO