一、網關路由
網關就是網絡的關口。數據在網絡間傳輸,從一個網絡傳輸到另一網絡時就需要經過網關來做數據的路由和轉發以及數據安全的校驗。
路由是網關的核心功能之一,決定如何將客戶端請求映射到后端服務。
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