單體架構時我們只需要完成一次用戶登錄、身份校驗,就可以在所有業務中獲取到用戶信息。而微服務拆分后,每個微服務都獨立部署,這就存在一些問題:
-
每個微服務都需要編寫登錄校驗、用戶信息獲取的功能嗎?
-
當微服務之間調用時,該如何傳遞用戶信息?
我們會通過網關技術解決上述問題:
第一章:網關路由,解決前端請求入口的問題。
第二章:網關鑒權,解決統一登錄校驗和用戶信息獲取的問題。
第三章:統一配置管理,解決微服務的配置文件重復和配置熱更新問題。
1.網關路由
1.1.認識網關
什么是網關?
顧明思議,網關就是網絡的關口。數據在網絡間傳輸,從一個網絡傳輸到另一網絡時就需要經過網關來做數據的路由和轉發以及數據安全的校驗。
更通俗的來講,網關就像是以前園區傳達室的大爺。
-
外面的人要想進入園區,必須經過大爺的認可,如果你是不懷好意的人,肯定被直接攔截。
-
外面的人要傳話或送信,要找大爺。大爺幫你帶給目標人。
現在,微服務網關就起到同樣的作用。前端請求不能直接訪問微服務,而是要請求網關:
-
網關可以做安全控制,也就是登錄身份校驗,校驗通過才放行
-
通過認證后,網關再根據請求判斷應該訪問哪個微服務,將請求轉發過去
在SpringCloud當中,提供了兩種網關實現方案:
-
Netflix Zuul:早期實現,目前已經淘汰
-
SpringCloudGateway:基于Spring的WebFlux技術,完全支持響應式編程,吞吐能力更強
1.2.快速入門
接下來,我們先看下如何利用網關實現請求路由。由于網關本身也是一個獨立的微服務,因此也需要創建一個模塊開發功能。大概步驟如下:
-
創建網關微服務
-
引入SpringCloudGateway、NacosDiscovery依賴
-
編寫啟動類
-
配置網關路由
1.2.1.配置路由
在hm-gateway
模塊的resources
目錄新建一個application.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代表負載均衡,會從注冊中心拉取服務列表predicates: # 路由斷言,判斷當前請求是否符合當前規則,符合則路由到目標服務- Path=/items/**,/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/**
1.3.路由過濾
路由規則的定義語法如下:
spring:cloud:gateway:routes:- id: itemuri: lb://item-servicepredicates:- Path=/items/**,/search/**
四個屬性含義如下:
-
id
:路由的唯一標示 -
predicates
:路由斷言,其實就是匹配條件 -
filters
:路由過濾條件,后面講 -
uri
:路由目標地址,lb://
代表負載均衡,從注冊中心獲取目標微服務的實例列表,并且負載均衡選擇一個訪問。
這里重點關注predicates
,也就是路由斷言。SpringCloudGateway中支持的斷言類型有很多:
名稱 | 說明 | 示例 |
---|---|---|
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.網關登錄校驗
單體架構時我們只需要完成一次用戶登錄、身份校驗,就可以在所有業務中獲取到用戶信息。而微服務拆分后,每個微服務都獨立部署,不再共享數據。也就意味著每個微服務都需要做登錄校驗,這顯然不可取。
2.1.鑒權思路分析
我們的登錄是基于JWT來實現的,校驗JWT的算法復雜,而且需要用到秘鑰。如果每個微服務都去做登錄校驗,這就存在著兩大問題:
-
每個微服務都需要知道JWT的秘鑰,不安全
-
每個微服務重復編寫登錄校驗代碼、權限校驗代碼,麻煩
既然網關是所有微服務的入口,一切請求都需要先經過網關。我們完全可以把登錄校驗的工作放到網關去做,這樣之前說的問題就解決了:
-
只需要在網關和用戶服務保存秘鑰
-
只需要在網關開發登錄校驗功能
此時,登錄校驗的流程如圖:
不過,這里存在幾個問題:
-
網關路由是配置的,請求轉發是Gateway內部代碼,我們如何在轉發之前做登錄校驗?
-
網關校驗JWT之后,如何將用戶信息傳遞給微服務?
-
微服務之間也會相互調用,這種調用不經過網關,又該如何傳遞用戶信息?
這些問題將在接下來幾節一一解決。
2.2.網關過濾器
登錄校驗必須在請求轉發到微服務之前做,否則就失去了意義。而網關的請求轉發是Gateway
內部代碼實現的,要想在請求轉發之前做登錄校驗,就必須了解Gateway
內部工作的基本原理。
如圖所示:
-
客戶端請求進入網關后由
HandlerMapping
對請求做判斷,找到與當前請求匹配的路由規則(Route
),然后將請求交給WebHandler
去處理。 -
WebHandler
則會加載當前路由下需要執行的過濾器鏈(Filter chain
),然后按照順序逐一執行過濾器(后面稱為Filter
)。 -
圖中
Filter
被虛線分為左右兩部分,是因為Filter
內部的邏輯分為pre
和post
兩部分,分別會在請求路由到微服務之前和之后被執行。 -
只有所有
Filter
的pre
邏輯都依次順序執行通過后,請求才會被路由到微服務。 -
微服務返回結果后,再倒序執行
Filter
的post
邏輯。 -
最終把響應結果返回。
如圖中所示,最終請求轉發是有一個名為NettyRoutingFilter
的過濾器來執行的,而且這個過濾器是整個過濾器鏈中順序最靠后的一個。如果我們能夠定義一個過濾器,在其中實現登錄校驗邏輯,并且將過濾器執行順序定義到NettyRoutingFilter
之前,這就符合我們的需求了!
2.3.自定義過濾器
2.3.1.自定義GatewayFilter
自定義GatewayFilter
不是直接實現GatewayFilter
,而是實現AbstractGatewayFilterFactory
。最簡單的方式是這樣的:
@Component
public class PrintAnyGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {@Overridepublic GatewayFilter apply(Object config) {return new GatewayFilter() {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 獲取請求ServerHttpRequest request = exchange.getRequest();// 編寫過濾器邏輯System.out.println("過濾器執行了");// 放行return chain.filter(exchange);}};}
}
注意:該類的名稱一定要以
GatewayFilterFactory
為后綴!
然后在yaml配置中這樣使用:
spring:cloud:gateway:default-filters:- PrintAny # 此處直接以自定義的GatewayFilterFactory類名稱前綴類聲明過濾器
另外,這種過濾器還可以支持動態配置參數,不過實現起來比較復雜,示例:
@Component
public class PrintAnyGatewayFilterFactory // 父類泛型是內部類的Config類型extends AbstractGatewayFilterFactory<PrintAnyGatewayFilterFactory.Config> {@Overridepublic GatewayFilter apply(Config config) {// OrderedGatewayFilter是GatewayFilter的子類,包含兩個參數:// - GatewayFilter:過濾器// - int order值:值越小,過濾器執行優先級越高return new OrderedGatewayFilter(new GatewayFilter() {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 獲取config值String a = config.getA();String b = config.getB();String c = config.getC();// 編寫過濾器邏輯System.out.println("a = " + a);System.out.println("b = " + b);System.out.println("c = " + c);// 放行return chain.filter(exchange);}}, 100);}// 自定義配置屬性,成員變量名稱很重要,下面會用到@Datastatic class Config{private String a;private String b;private String c;}// 將變量名稱依次返回,順序很重要,將來讀取參數時需要按順序獲取@Overridepublic List<String> shortcutFieldOrder() {return List.of("a", "b", "c");}// 返回當前配置類的類型,也就是內部的Config@Overridepublic Class<Config> getConfigClass() {return Config.class;}}
然后在yaml文件中使用:
spring:cloud:gateway:default-filters:- PrintAny=1,2,3 # 注意,這里多個參數以","隔開,將來會按照shortcutFieldOrder()方法返回的參數順序依次復制
上面這種配置方式參數必須嚴格按照shortcutFieldOrder()方法的返回參數名順序來賦值。
還有一種用法,無需按照這個順序,就是手動指定參數名:
spring:cloud:gateway:default-filters:- name: PrintAnyargs: # 手動指定參數名,無需按照參數順序a: 1b: 2c: 3
2.3.2.自定義GlobalFilter
自定義GlobalFilter則簡單很多,直接實現GlobalFilter即可,而且也無法設置動態參數:
@Component
public class PrintAnyGlobalFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 編寫過濾器邏輯System.out.println("未登錄,無法訪問");// 放行// return chain.filter(exchange);// 攔截ServerHttpResponse response = exchange.getResponse();response.setRawStatusCode(401);return response.setComplete();}@Overridepublic int getOrder() {// 過濾器執行順序,值越小,優先級越高return 0;}
}
2.4.登錄校驗
接下來,我們就利用自定義GlobalFilter
來完成登錄校驗。
2.4.1.JWT工具
具體作用如下:
-
AuthProperties
:配置登錄校驗需要攔截的路徑,因為不是所有的路徑都需要登錄才能訪問 -
JwtProperties
:定義與JWT工具有關的屬性,比如秘鑰文件位置 -
SecurityConfig
:工具的自動裝配 -
JwtTool
:JWT工具,其中包含了校驗和解析token
的功能 -
hmall.jks
:秘鑰文件
其中AuthProperties
和JwtProperties
所需的屬性要在application.yaml
中配置:
hm:jwt:location: classpath:hmall.jks # 秘鑰地址alias: hmall # 秘鑰別名password: hmall123 # 秘鑰文件密碼tokenTTL: 30m # 登錄有效期auth:excludePaths: # 無需登錄校驗的路徑- /search/**- /users/login- /items/**
2.4.2.登錄校驗過濾器
接下來,我們定義一個登錄校驗的過濾器:
代碼如下:
package com.hmall.gateway.filter;import com.hmall.common.exception.UnauthorizedException;
import com.hmall.common.utils.CollUtils;
import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.util.JwtTool;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import java.util.List;@Component
@RequiredArgsConstructor
@EnableConfigurationProperties(AuthProperties.class)
public class AuthGlobalFilter implements GlobalFilter, Ordered {private final JwtTool jwtTool;private final AuthProperties authProperties;private final AntPathMatcher antPathMatcher = new AntPathMatcher();@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 (!CollUtils.isEmpty(headers)) {token = headers.get(0);}// 4.校驗并解析tokenLong userId = null;try {userId = jwtTool.parseToken(token);} catch (UnauthorizedException e) {// 如果無效,攔截ServerHttpResponse response = exchange.getResponse();response.setRawStatusCode(401);return response.setComplete();}// TODO 5.如果有效,傳遞用戶信息System.out.println("userId = " + userId);// 6.放行return chain.filter(exchange);}private boolean isExclude(String antPath) {for (String pathPattern : authProperties.getExcludePaths()) {if(antPathMatcher.match(pathPattern, antPath)){return true;}}return false;}@Overridepublic int getOrder() {return 0;}
}
2.5.微服務獲取用戶
現在,網關已經可以完成登錄校驗并獲取登錄用戶身份信息。但是當網關將請求轉發到微服務時,微服務又該如何獲取用戶身份呢?
由于網關發送請求到微服務依然采用的是Http
請求,因此我們可以將用戶信息以請求頭的方式傳遞到下游微服務。然后微服務可以從請求頭中獲取登錄用戶信息。考慮到微服務內部可能很多地方都需要用到登錄用戶信息,因此我們可以利用SpringMVC的攔截器來實現登錄用戶信息獲取,并存入ThreadLocal,方便后續使用。
據圖流程圖如下:
因此,接下來我們要做的事情有:
-
改造網關過濾器,在獲取用戶信息后保存到請求頭,轉發到下游微服務
-
編寫微服務攔截器,攔截請求獲取用戶信息,保存到ThreadLocal后放行
2.5.1.保存用戶到請求頭
首先,我們修改登錄校驗攔截器的處理邏輯,保存用戶信息到請求頭中:
修改請求,添加一個含用戶信息的請求頭到請求中,從而實現用戶信息的傳遞
2.5.2.攔截器獲取用戶
在hm-common中已經有一個用于保存登錄用戶的ThreadLocal工具:
其中已經提供了保存和獲取用戶的方法:
接下來,我們只需要編寫攔截器,獲取用戶信息并保存到UserContext
,然后放行即可。
類似于蒼穹外賣中的BaseContext
由于每個微服務都有獲取登錄用戶的需求,因此攔截器我們直接寫在hm-common
中,并寫好自動裝配。這樣微服務只需要引入hm-common
就可以直接具備攔截器功能,無需重復編寫。
我們在hm-common
模塊下定義一個攔截器:
具體代碼如下:
package com.hmall.common.interceptor;import cn.hutool.core.util.StrUtil;
import com.hmall.common.utils.UserContext;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class UserInfoInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.獲取請求頭中的用戶信息String userInfo = request.getHeader("user-info");// 2.判斷是否為空if (StrUtil.isNotBlank(userInfo)) {// 不為空,保存到ThreadLocalUserContext.setUser(Long.valueOf(userInfo));}// 3.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 移除用戶UserContext.removeUser();}
}
接著在hm-common
模塊下編寫SpringMVC
的配置類,配置登錄攔截器:
想要攔截器生效,還需要對應的配置類
具體代碼如下:
@Configuration
public class MvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry){registry.addInterceptor(new UserInfoInterceptor());}}
不過,需要注意的是,這個配置類默認是不會生效的,因為它所在的包與其它微服務的掃描包不一致,無法被掃描到,因此無法生效。
@Configuration
@ConditionalOnClass(DispatcherServlet.class)
public class MvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry){registry.addInterceptor(new UserInfoInterceptor());}}
添加@ConditionalOnClass(DispatcherServlet.class)的原因:
????????因為hm-gateway中也引用了common模塊,而我們知道gateway使用的openfeign雖然也處理http請求,但是底層不是靠SpringMvc實現的,而現在設計了只要引用common這個包就會自動裝配MvcConfig配置類,然而hm-gateway底層沒有springmvc自然無法自動裝配MvcConfig配置類,會報錯。
????????所以我們需要設計成只有依賴了SpringMvc的微服務模塊才自動裝配,而SpringMvc的核心類就是DispatcherServlet.class,所以只有有DispatcherServlet.class這個類的微服務模塊才會自動裝配MvcConfig配置類
基于SpringBoot的自動裝配原理,我們要將其添加到resources
目錄下的META-INF/spring.factories
文件中:
2.5.3.恢復購物車代碼
之前我們無法獲取登錄用戶,所以把購物車服務的登錄用戶寫死了,現在需要恢復到原來的樣子。
找到cart-service
模塊的com.hmall.cart.service.impl.CartServiceImpl
:
修改其中的queryMyCarts
方法:
2.6.OpenFeign傳遞用戶
前端發起的請求都會經過網關再到微服務,由于我們之前編寫的過濾器和攔截器功能,微服務可以輕松獲取登錄用戶信息。
但有些業務是比較復雜的,請求到達微服務后還需要調用其它多個微服務。比如下單業務,流程如下:
下單的過程中,需要調用商品服務扣減庫存,調用購物車服務清理用戶購物車。而清理購物車時必須知道當前登錄的用戶身份。但是,訂單服務調用購物車時并沒有傳遞用戶信息,購物車服務無法知道當前用戶是誰!
由于微服務獲取用戶信息是通過攔截器在請求頭中讀取,因此要想實現微服務之間的用戶信息傳遞,就必須在微服務發起調用時把用戶信息存入請求頭。
微服務之間調用是基于OpenFeign來實現的,并不是我們自己發送的請求。我們如何才能讓每一個由OpenFeign發起的請求自動攜帶登錄用戶信息呢?
這里要借助Feign中提供的一個攔截器接口:
public interface RequestInterceptor {/*** Called for every request. * Add data using methods on the supplied {@link RequestTemplate}.*/void apply(RequestTemplate template);
}
我們只需要實現這個接口,然后實現apply方法,利用RequestTemplate
類來添加請求頭,將用戶信息保存到請求頭中。這樣以來,每次OpenFeign發起請求的時候都會調用該方法,傳遞用戶信息。
由于FeignClient
全部都是在hm-api
模塊,因此我們在hm-api
模塊的com.hmall.api.config.DefaultFeignConfig
中編寫這個攔截器:
在com.hmall.api.config.DefaultFeignConfig
中添加一個Bean:
@Bean
public RequestInterceptor userInfoRequestInterceptor(){return new RequestInterceptor() {@Overridepublic void apply(RequestTemplate template) {// 獲取登錄用戶Long userId = UserContext.getUser();if(userId == null) {// 如果為空則直接跳過return;}// 如果不為空則放入請求頭中,傳遞給下游微服務template.header("user-info", userId.toString());}};
}
好了,現在微服務之間通過OpenFeign調用時也會傳遞登錄用戶信息了。
總結:微服務登陸解決方案
3.配置管理
到目前為止我們已經解決了微服務相關的幾個問題:
-
微服務遠程調用
-
微服務注冊、發現
-
微服務請求路由、負載均衡
-
微服務登錄用戶信息傳遞
不過,現在依然還有幾個問題需要解決:
這些問題都可以通過統一的配置管理器服務解決。而Nacos不僅僅具備注冊中心功能,也具備配置管理的功能:
微服務共享的配置可以統一交給Nacos保存和管理,在Nacos控制臺修改配置后,Nacos會將配置變更推送給相關的微服務,并且無需重啟即可生效,實現配置熱更新。
網關的路由同樣是配置,因此同樣可以基于這個功能實現動態路由功能,無需重啟網關即可修改路由配置。
3.1.配置共享
我們可以把微服務共享的配置抽取到Nacos中統一管理,這樣就不需要每個微服務都重復配置了。分為兩步:
-
在Nacos中添加共享配置
-
微服務拉取配置
3.1.1.添加共享配置
首先是jdbc相關配置,在配置管理
->配置列表
中點擊+
新建一個配置:
在彈出的表單中填寫信息:
其中詳細的配置如下:
spring:datasource:url: jdbc:mysql://${hm.db.host:192.168.150.101}:${hm.db.port:3306}/${hm.db.database}?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghaidriver-class-name: com.mysql.cj.jdbc.Driverusername: ${hm.db.un:root}password: ${hm.db.pw:123}
mybatis-plus:configuration:default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandlerglobal-config:db-config:update-strategy: not_nullid-type: auto
注意這里的jdbc的相關參數并沒有寫死,例如:
-
數據庫ip
:通過${hm.db.host:192.168.150.101}
配置了默認值為192.168.150.101
,同時允許通過${hm.db.host}
來覆蓋默認值 -
數據庫端口
:通過${hm.db.port:3306}
配置了默認值為3306
,同時允許通過${hm.db.port}
來覆蓋默認值 -
數據庫database
:可以通過${hm.db.database}
來設定,無默認值
然后是統一的日志配置,命名為shared-log.
yaml
,配置內容如下:
logging:level:com.hmall: debugpattern:dateformat: HH:mm:ss:SSSfile:path: "logs/${spring.application.name}"
然后是統一的swagger配置,命名為shared-swagger.yaml
,配置內容如下:
knife4j:enable: trueopenapi:title: ${hm.swagger.title:黑馬商城接口文檔}description: ${hm.swagger.description:黑馬商城接口文檔}email: ${hm.swagger.email:zhanghuyi@itcast.cn}concat: ${hm.swagger.concat:虎哥}url: https://www.itcast.cnversion: v1.0.0group:default:group-name: defaultapi-rule: packageapi-rule-resources:- ${hm.swagger.package}
注意,這里的swagger相關配置我們沒有寫死,例如:
-
title
:接口文檔標題,我們用了${hm.swagger.title}
來代替,將來可以有用戶手動指定 -
email
:聯系人郵箱,我們用了${hm.swagger.email:
zhanghuyi@itcast.cn
}
,默認值是zhanghuyi@itcast.cn
,同時允許用戶利用${hm.swagger.email}
來覆蓋。
3.1.2.拉取共享配置
接下來,我們要在微服務拉取共享配置。將拉取到的共享配置與本地的application.yaml
配置合并,完成項目上下文的初始化。
不過,需要注意的是,讀取Nacos配置是SpringCloud上下文(ApplicationContext
)初始化時處理的,發生在項目的引導階段。然后才會初始化SpringBoot上下文,去讀取application.yaml
。
也就是說引導階段,application.yaml
文件尚未讀取,根本不知道nacos 地址,該如何去加載nacos中的配置文件呢?
SpringCloud在初始化上下文的時候會先讀取一個名為bootstrap.yaml
的文件,如果我們將nacos地址配置到其中,那么在項目引導階段就可以讀取nacos中的配置了。
因此,微服務整合Nacos配置管理的步驟如下:
1)引入依賴:
在cart-service模塊引入依賴:
<!--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
在cart-service中的resources目錄新建一個bootstrap.yaml文件:
內容如下:
spring:application:name: cart-service # 服務名稱profiles:active: devcloud:nacos:server-addr: 192.168.150.101 # nacos地址config:file-extension: yaml # 文件后綴名shared-configs: # 共享配置- dataId: shared-jdbc.yaml # 共享mybatis配置- dataId: shared-log.yaml # 共享日志配置- dataId: shared-swagger.yaml # 共享日志配置
3)修改application.yaml
由于一些配置挪到了bootstrap.yaml,因此application.yaml需要修改為:
server:port: 8082
feign:okhttp:enabled: true # 開啟OKHttp連接池支持
hm:swagger:title: 購物車服務接口文檔package: com.hmall.cart.controllerdb:database: hm-cart
重啟服務,發現所有配置都生效了。
3.2.配置熱更新
有很多的業務相關參數,將來可能會根據實際情況臨時調整。例如購物車業務,購物車數量有一個上限,默認是10,對應代碼如下:
現在這里購物車是寫死的固定值,我們應該將其配置在配置文件中,方便后期修改。
但現在的問題是,即便寫在配置文件中,修改了配置還是需要重新打包、重啟服務才能生效。能不能不用重啟,直接生效呢?
這就要用到Nacos的配置熱更新能力了,分為兩步:
-
在Nacos中添加配置
-
在微服務讀取配置
3.2.1.添加配置到Nacos
首先,我們在nacos中添加一個配置文件,將購物車的上限數量添加到配置中:
注意文件的dataId格式:
[服務名]-[spring.active.profile].[后綴名]
文件名稱由三部分組成:
-
服務名
:我們是購物車服務,所以是cart-service
-
spring.active.profile
:就是spring boot中的spring.active.profile
,可以省略,則所有profile共享該配置 -
后綴名
:例如yaml
這里我們直接使用cart-service.yaml
這個名稱,則不管是dev還是local環境都可以共享該配置。
配置內容如下:
hm:cart:maxAmount: 1 # 購物車商品數量上限
提交配置,在控制臺能看到新添加的配置:
3.2.2.配置熱更新
接著,我們在微服務中讀取配置,實現配置熱更新。
在cart-service
中新建一個屬性讀取類:
代碼如下:
package com.hmall.cart.config;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;@Data
@Component
@ConfigurationProperties(prefix = "hm.cart")
public class CartProperties {private Integer maxAmount;
}
接著,在業務中使用該屬性加載類:
測試,向購物車中添加多個商品:
我們在nacos控制臺,將購物車上限配置為5:
無需重啟,再次測試購物車功能:
成功實現!