【 SpringCloud | 微服務 網關技術 】

單體架構時我們只需要完成一次用戶登錄、身份校驗,就可以在所有業務中獲取到用戶信息。而微服務拆分后,每個微服務都獨立部署,這就存在一些問題:

  • 每個微服務都需要編寫登錄校驗、用戶信息獲取的功能嗎?

  • 當微服務之間調用時,該如何傳遞用戶信息?

我們會通過網關技術解決上述問題:

  • 第一章:網關路由,解決前端請求入口的問題。

  • 第二章:網關鑒權,解決統一登錄校驗和用戶信息獲取的問題。

  • 第三章:統一配置管理,解決微服務的配置文件重復和配置熱更新問題。


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內部工作的基本原理。

如圖所示:

  1. 客戶端請求進入網關后由HandlerMapping對請求做判斷,找到與當前請求匹配的路由規則(Route),然后將請求交給WebHandler去處理。

  2. WebHandler則會加載當前路由下需要執行的過濾器鏈(Filter chain),然后按照順序逐一執行過濾器(后面稱為Filter)。

  3. 圖中Filter被虛線分為左右兩部分,是因為Filter內部的邏輯分為prepost兩部分,分別會在請求路由到微服務之前之后被執行。

  4. 只有所有Filterpre邏輯都依次順序執行通過后,請求才會被路由到微服務。

  5. 微服務返回結果后,再倒序執行Filterpost邏輯。

  6. 最終把響應結果返回。

如圖中所示,最終請求轉發是有一個名為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工具的自動裝配

  • JwtToolJWT工具,其中包含了校驗和解析token的功能

  • hmall.jks秘鑰文件

其中AuthPropertiesJwtProperties所需的屬性要在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:

無需重啟,再次測試購物車功能:

成功實現!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/83434.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/83434.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/83434.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

python,Dataframe基于所有包含某個關鍵字的列等于某個值過濾

在 Python 中&#xff0c;使用 Pandas 的 DataFrame 丟棄符合特定條件的行&#xff0c;條件為所有包含某個關鍵字的列中&#xff0c;等于某個值&#xff08;即所有包含某個關鍵字的列中等于某個值的行&#xff09;&#xff0c;可用以下方法實現&#xff1a; import pandas as …

50天50個小項目 (Vue3 + Tailwindcss V4) ? | Sound Board(音響控制面板)

&#x1f4c5; 我們繼續 50 個小項目挑戰&#xff01;—— SoundBoard 組件 倉庫地址&#xff1a;https://github.com/SunACong/50-vue-projects 項目預覽地址&#xff1a;https://50-vue-projects.vercel.app/ &#x1f3af; 組件目標 實現一個響應式按鈕面板&#xff0c;點…

在Ubuntu20.04上安裝ROS Noetic

本章教程,主要記錄在Ubuntu20.04上安裝ROS Noetic。 一、添加軟件源 sudo sh -c . /etc/lsb-release && echo "deb http://mirrors.tuna.tsinghua.edu.cn/ros/ubuntu/ `lsb_release -cs` main" > /etc/apt/sources.list.d/ros-latest.list二、設置秘鑰 …

神經網絡基礎:從單個神經元到多層網絡(superior哥AI系列第3期)

&#x1f9e0; 神經網絡基礎&#xff1a;從單個神經元到多層網絡&#xff08;superior哥AI系列第3期&#xff09; 哈嘍&#xff01;各位AI探索者們&#xff01;&#x1f44b; 上期我們把數學"怪獸"給馴服了&#xff0c;是不是感覺還挺輕松的&#xff1f;今天我們要進…

03 APP 自動化-定位元素工具元素定位

文章目錄 一、Appium常用元素定位工具1、U IAutomator View Android SDK 自帶的定位工具2、Appium Desktop Inspector3、Weditor安裝&#xff1a;Weditor工具的使用 4、uiautodev通過定位工具獲取app頁面元素有哪些屬性 二、app 元素定位方法 一、Appium常用元素定位工具 1、U…

Java消息隊列與安全實戰:謝飛機的燒餅攤故事

Java消息隊列與安全實戰&#xff1a;謝飛機的燒餅攤故事 第一輪&#xff1a;消息隊列與緩存 面試官&#xff1a;謝飛機&#xff0c;Kafka和RabbitMQ在電商場景如何選型&#xff1f; 謝飛機&#xff1a;&#xff08;摸出燒餅&#xff09;Kafka適合訂單日志處理&#xff0c;像…

Unity中的MonoSingleton<T>與Singleton<T>

1.MonoSingleton 代碼部分 using UnityEngine;/// <summary> /// MonoBehaviour單例基類 /// 需要掛載到GameObject上使用 /// </summary> public class MonoSingleton<T> : MonoBehaviour where T : MonoSingleton<T> {private static T _instance;…

day 40 python打卡

仔細學習下測試和訓練代碼的邏輯&#xff0c;這是基礎&#xff0c;這個代碼框架后續會一直沿用&#xff0c;后續的重點慢慢就是轉向模型定義階段了。 # 先繼續之前的代碼 import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataL…

進階日記(一)大模型的本地部署與運行

目錄 一、背景知識 為什么要在本地部署大模型&#xff1f; 在本地部署大模型需要做哪些準備工作&#xff1f; &#xff08;1&#xff09;硬件配置 &#xff08;2&#xff09;軟件環境 有哪些部署工具可供選擇&#xff1f; 二、Ollma安裝 Ollama安裝完之后&#xff0c;還…

Spring Boot Starter 自動裝配原理全解析:從概念到實踐

Spring Boot Starter 自動裝配原理全解析&#xff1a;從概念到實踐 在Spring Boot開發中&#xff0c;Starter和自動裝配是兩個核心概念&#xff0c;它們共同構成了“開箱即用”的開發體驗。通過引入一個Starter依賴&#xff0c;開發者可以快速集成第三方組件&#xff08;如Red…

win11回收站中出現:查看回收站中是否有以下項: WPS云盤回收站

好久沒更新了&#xff0c;首先祝所有大朋友、小朋友六一兒童節快樂&#xff0c;真的希望我們永遠都不會長大呀&#xff0c;長大真的好累呀(?_?) 免責聲明 筆者先來個免責聲明吧&#xff0c;被網上的陰暗面嚇到了 若讀者參照筆者的這篇文章所執行的操作中途或后續出現的任何…

網絡安全大模型理解

一、網絡安全大模型的概述 網絡安全大模型是一種用于識別和應對各種網絡安全威脅的模型。它通過分析網絡數據包、網絡行為等信息&#xff0c;識別潛在的網絡安全事件&#xff0c;并采取相應的措施進行防御。網絡安全大模型主要包括以下幾個部分&#xff1a; 1. 數據預處理&am…

C++語法架構解說

C 是一種功能強大且靈活的編程語言&#xff0c;廣泛應用于系統編程、游戲開發、嵌入式系統、金融軟件等領域。 其語法架構復雜且豐富&#xff0c;涵蓋了從基礎語法到高級特性的各個方面。 對 C 語法架構 的詳細解析&#xff0c;涵蓋其核心語法結構、面向對象編程&#xff08;…

審計- 1- 審計概述

1.財務報表審計的概念 財務報表審計是指注冊會計師對財務報表是否不存在重大錯報提供合理保證&#xff0c;以積極方式提出意見&#xff0c;增強除管理層之外的預期使用者對財務報表信賴的程度。 1.1 審計業務三方關系人 注冊會計師對財務報表發表審計意見是注冊會計師的責任管…

RapidOCR集成PP-OCRv5_det mobile模型記錄

該文章主要摘取記錄RapidOCR集成PP-OCRv5_mobile_det記錄&#xff0c;涉及模型轉換&#xff0c;模型精度測試等步驟。原文請前往官方博客&#xff1a; https://rapidai.github.io/RapidOCRDocs/main/blog/2025/05/26/rapidocr%E9%9B%86%E6%88%90pp-ocrv5_det%E6%A8%A1%E5%9E%8B…

Spine工具入門教程2之導入

1、導入定義 從原畫轉化為Spine的環節。 &#xff08;1&#xff09;選擇路徑&#xff0c;拖動圖片導入方式 缺點&#xff1a;定位不準 【使用批量導出的方式】 在PS工具中&#xff0c;選擇所有圖層后右鍵選擇導出。 在Spine工具中&#xff0c;選擇路徑導入圖片。 然后再拖…

【03】完整開發騰訊云播放器SDK的UniApp官方UTS插件——優雅草上架插件市場-卓伊凡

【03】完整開發騰訊云播放器SDK的UniApp官方UTS插件——優雅草上架插件市場-卓伊凡 一、項目背景與轉型原因 1.1 原定計劃的變更 本系列教程最初規劃是開發即構美顏SDK的UTS插件&#xff0c;但由于甲方公司內部戰略調整&#xff0c;原項目被迫中止。考慮到&#xff1a; 技術…

戴爾AI服務器訂單激增至121億美元,但傳統業務承壓

戴爾科技121億美元的AI服務器訂單&#xff0c;不僅超過了公司整個2025財年的AI服務器出貨量&#xff0c;更讓其AI訂單積壓達到144億美元的歷史高位。 戴爾科技最新財報顯示&#xff0c;AI服務器需求的爆炸式增長正在重塑這家老牌PC制造商的業務格局&#xff0c;但同時也暴露出…

多線程和并發之線程

線程 前面講到進程&#xff1a;為了并發執行任務&#xff08;程序&#xff09;&#xff0c;現代操作系統才引進進程的概念 分析&#xff1a; 創建開銷問題&#xff1a;創建一個進程開銷&#xff1a;大 子進程需要拷貝父進程的整個地址空間 通信開銷問題&#xff1a;進程間的通…

AAAI 2025論文分享│STD-PLM:基于預訓練語言模型的時空數據預測與補全方法

本文詳細介紹了一篇發表于人工智能頂級會議AAAI 2025的論文《STD-PLM: Understanding Both Spatial and Temporal Properties of Spatial-Temporal Data with PLM》。該論文提出了一種基于預訓練語言模型&#xff08;Pre-trained Language Model?&#xff0c;PLM&#xff09;的…