五、SpringCloud alibaba 之 閘道器GateWay

阿瞒123發表於2024-05-26

詳細的閘道器學習可以參考:

https://www.jianshu.com/p/8749dfe9832e

https://www.zhihu.com/column/c_1357481230536216576

https://www.cnblogs.com/bjlhx/category/1273947.html

6.1、閘道器介紹

什麼是閘道器

顧明思議,閘道器就是絡的口。資料在網路間傳輸,從一個網路傳輸到另一網路時就需要經過閘道器來做資料的路由和轉發以及資料安全的校驗

更通俗的來講,閘道器就像是以前園區傳達室的大爺。

  • 外面的人要想進入園區,必須經過大爺的認可,如果你是不懷好意的人,肯定被直接攔截。

  • 外面的人要傳話或送信,要找大爺。大爺幫你帶給目標人。

現在,微服務閘道器就起到同樣的作用。前端請求不能直接訪問微服務,而是要請求閘道器:

  • 閘道器可以做安全控制,也就是登入身份校驗,校驗透過才放行

  • 透過認證後,閘道器再根據請求判斷應該訪問哪個微服務,將請求轉發過去

在SpringCloud當中,提供了兩種閘道器實現方案:

  • Netflix Zuul:早期實現,目前已經淘汰

  • Spring Cloud Gateway 是由 WebFlux + Netty + Reactor 實現的響應式的 API 閘道器。它不能在傳統的 servlet 容器中工作,也不能構建成 war包。Spring Cloud Gateway 旨在為微服務架構提供一種簡單且有效的 API 路由的管理方式,並基於Filter 的方式提供閘道器的基本功能,例如說安全認證、監控、限流等等。

課堂中我們以SpringCloudGateway為例來講解,官方網站:https://spring.io/projects/spring-cloud-gateway/#learn

6.2、快速入門

我們先看下如何利用閘道器實現請求路由。由於閘道器本身也是一個獨立的微服務,因此也需要建立一個模組開發功能。大概步驟如下:

  • 建立閘道器微服務

  • 引入SpringCloudGateway、NacosDiscovery依賴

  • 編寫啟動類

  • 配置閘道器路由

建立一個module

首先,我們要在spring-cloud下建立一個新的module,命名為spring-cloud-gateway,作為閘道器微服務:

引入依賴

1、閘道器服務也是一個微服務,因此需要引入spring-cloud依賴

2、閘道器服務需要把前端的請求轉發給別的微服務,因此需要引入閘道器依賴

3、閘道器服務要做請求轉發就需要知道其他微服務的地址。閘道器服務獲取其他服務地址的方式也是透過註冊中心獲取,因此需要引入nacos相關依賴。

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
            <version>${spring.boot.version}</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>

配置yml檔案

server:
  port: 8080
spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        username: nacos
        server-addr: localhost:8848
        password: 123456
        namespace: 495e2ede-c37a-4258-8e11-72f21b0cf1cd
        group: java-coder
    gateway:
      routes:
        - id: order-service # 路由規則id,自定義,唯一
          uri: lb://order-service # 路由的目標服務,lb代表負載均衡,會從註冊中心拉取服務列表
          predicates: # 路由斷言,判斷當前請求是否符合當前規則,符合則路由到目標服務
            - Path=/order/** # 這裡是以請求路徑作為判斷規則
        - id: stock-service
          uri: lb://stock-service
          predicates:
            - Path=/stock/**
feign:
  okhttp:
    enabled: true # 開啟OKHttp功能

建立啟動類

package com.java.coder.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication springApplication=new SpringApplication();
        springApplication.run(GatewayApplication.class,args);
    }
}

執行GatewayApplication,在日期中我們可以看到閘道器服務是執行在netty中的,我們在引入依賴時,也沒有引入spring-boot-starter-web依賴,正如前面說的,Spring Cloud Gateway 是由 WebFlux + Netty + Reactor 實現的響應式的 API 閘道器。它不能在傳統的 servlet 容器中工作,也不能構建成 war包

透過8081介面直接訪問order-service:http://localhost:8081/order/feignTest

執行結果如下:

透過8080介面訪問gateway閘道器,然後閘道器把請求轉發到order-service:http://localhost:8080/order/feignTest

整體的呼叫過程如下:

6.3、核心概念

https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-starter

6.3.1、路由

路由(route) :路由是閘道器中最基礎的部分,路由資訊包括一個ID、一個目的URI、一組斷言工廠、一組Filter組成。如果斷言為真,則說明請求的URL和配置的路由匹配。

路由示例

spring:
  cloud:
    gateway:
      routes: # 路由陣列[路由 就是指定當請求滿足什麼條件的時候轉到哪個微服務]
        - id: order-service # 路由規則id,自定義,唯一
          uri: lb://order-service # 路由的目標服務,lb代表負載均衡,會從註冊中心拉取服務列表,uri也可以直接寫地址
          order: 1 #路由的優先順序,數字越小級別越高
          predicates: # 斷言(就是路由轉發要滿足的條件) ,判斷當前請求是否符合當前規則,符合則路由到目標服務
            - Path=/order/** # 當請求路徑滿足Path指定的規則時,才進行路由轉發
          filters: #過濾器,請求在傳遞過程中可以透過過濾器對其進行一定的修改
            ‐StripPrefix=1 #轉發之前去掉1層路徑

gateway的配置檔案

spring.cloud.gateway對應的配置類是GatewayProperties

@ConfigurationProperties(GatewayProperties.PREFIX)
@Validated
public class GatewayProperties {
	/**
	 * 屬性字首
	 */
	public static final String PREFIX = "spring.cloud.gateway";
	/**
	 * 路由列表
	 */
	@NotNull
	@Valid
	private List<RouteDefinition> routes = new ArrayList<>();
	/**
	 * 被應用到所有路由的過濾器定義列表,這些過濾器會作用到所有的路由
	 */
	private List<FilterDefinition> defaultFilters = new ArrayList<>();

	private List<MediaType> streamingMediaTypes = Arrays
			.asList(MediaType.TEXT_EVENT_STREAM, MediaType.APPLICATION_STREAM_JSON);
	private boolean failOnRouteDefinitionError = true;

	private Metrics metrics = new Metrics();
}

從上述的配置檔案我們可以看到,routes其實就是一個RouteDefinition的列表。

RouteDefinition

RouteDefinition的定義如下:

public class RouteDefinition {
	// 路由id
	private String id;
	// 路由斷言
	@NotEmpty
	@Valid
	private List<PredicateDefinition> predicates = new ArrayList<>();
	// 路由過濾器的定義
	@Valid
	private List<FilterDefinition> filters = new ArrayList<>();
	// 要路由的地址
	@NotNull
	private URI uri;
	// 
	private Map<String, Object> metadata = new HashMap<>();
	// 優先順序
	private int order = 0;
}

四個屬性含義如下:

  • id:路由的唯一標示

  • predicates:路由斷言,其實就是匹配條件

  • filters:路由過濾條件,後面講

  • uri:路由目標地址,lb://代表負載均衡,從註冊中心獲取目標微服務的例項列表,並且負載均衡選擇一個訪問。uri也可以直接配置成訪問路徑,比如http://localhost:8082

spring:
  cloud:
    gateway:
      routes:
        - id: route-service
          uri: http://localhost:8082
          predicates:
              - Path=/stock/**

6.3.2、斷言

斷言(predicates) :Java8中的斷言函式,SpringCloud Gateway中的斷言函式型別是Spring5.0框架中的ServerWebExchange。斷言函式允許開發者去定義匹配Http request中的任何資訊,比如請求頭和引數等。

PredicateDefinition

斷言對應的類就是PredicateDefinition

@Validated
public class PredicateDefinition {

	@NotNull
	private String name;

	private Map<String, String> args = new LinkedHashMap<>();
}

SpringCloudGateway中支援的斷言型別有很多,這些斷言的實現都是由spring cloud gateway的斷言工廠來實現的:

名稱 說明 示例
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 權重處理

基於Datetime型別的斷言工廠

此型別的斷言根據時間做判斷,主要有三個:

AfterRoutePredicateFactory: 接收一個日期引數,判斷請求日期是否晚於指定日期

BeforeRoutePredicateFactory: 接收一個日期引數,判斷請求日期是否早於指定日期

BetweenRoutePredicateFactory: 接收兩個日期引數,判斷請求日期是否在指定時間段內

時間的格式可以透過ZonedDateTime.now()來獲取

基於遠端地址的斷言工廠

RemoteAddrRoutePredicateFactory:接收一個IP地址段,判斷請求主機地址是否在地址段中

基於Cookie的斷言工廠

CookieRoutePredicateFactory:接收兩個引數,cookie 名字和一個正規表示式。 判斷請求

cookie是否具有給定名稱且值與正規表示式匹配。

基於Header的斷言工廠

HeaderRoutePredicateFactory:接收兩個引數,標題名稱和正規表示式。 判斷請求Header是否具有給定名稱且值與正規表示式匹配。

基於Host的斷言工廠

HostRoutePredicateFactory:接收一個引數,主機名模式。判斷請求的Host是否滿足匹配規則。

基於Method請求方法的斷言工廠

MethodRoutePredicateFactory:接收一個引數,判斷請求型別是否跟指定的型別匹配。

基於Path請求路徑的斷言工廠

PathRoutePredicateFactory:接收一個引數,判斷請求的URI部分是否滿足路徑規則。

基於Query請求引數的斷言工廠

QueryRoutePredicateFactory :接收兩個引數,請求param和正規表示式, 判斷請求引數是否具有給定名稱且值與正規表示式匹配。

6.3.3、自定義斷言工廠

自定義路由斷言工廠需要繼承 AbstractRoutePredicateFactory 類,重寫 apply 方法的邏輯。在 apply 方法中可以透過 exchange.getRequest() 拿到 ServerHttpRequest 物件,從而可以獲取到請求的引數、請求方式、請求頭等資訊。

1、 必須spring元件 bean
2、 類必須加上RoutePredicateFactory作為結尾
3、 必須繼承AbstractRoutePredicateFactory
4、 必須宣告靜態內部類  宣告屬性來接收 配置檔案中對應的斷言的資訊
5、 需要結合shortcutFieldOrder進行繫結
6、 透過apply進行邏輯判斷 true就是匹配成功  false匹配失敗

定義一個斷言工廠

@Slf4j
@Component
public class CheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> {


    public CheckAuthRoutePredicateFactory() {
        super(Config.class);
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return new Predicate<ServerWebExchange>() {
            @Override
            public boolean test(ServerWebExchange serverWebExchange) {
                log.info("呼叫CheckAuthRoutePredicateFactory" +config.getName());
                if(config.getName().equals("java-coder")){
                    return true;
                }
                return false;
            }
        };
    }

    /**
     * Returns hints about the number of args and the order for shortcut parsing.
     * 返回有關引數數量和快捷方式分析順序的提示。
     *
     * @return the list of hints
     */
    @Override
    public List<String> shortcutFieldOrder() {
        return Collections.singletonList("name");
    }


    /**
     * 用於接收配置資訊
     */
    public static class Config{
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

在配置檔案中使用自定義的過濾器工廠

spring:
  cloud:
    gateway:
      routes:
        - id: route-service
          uri: http://localhost:8082
          predicates:
              - Path=/stock/**
              - CheckAuth=xushu #此時就在配置檔案中使用了自定義的斷言工廠

6.3.4、過濾器

過濾器(Filter) :SpringCloud Gateway中的filter分為閘道器過濾器 Gateway FilIer全域性過濾器Global Filter。內建Filter都實現GatewayFilter介面。

GateFilter:預設是不生效的,要配置到特定的路由下才會針對特定的路由生效。如果想要GateFilter對所有的路由都生效,需要把GateFilter配置在default-filters下面。

GlobalFilter: 全域性過濾器,作用範圍是所有路由;宣告之後直接生效。

兩種過濾器內部基本一致,都只有一個方法filter

Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);

內建過濾器

SpringCloud Gateway中內建了很多的過濾器工廠,我們透過一些過濾器工廠可以進行一些業務邏輯處理器,比如新增剔除響應頭,新增去除引數等

名稱 說明
AddRequestHeader 給當前請求新增一個請求頭
AddRequestParameter 為原始請求新增請求引數
AddResponseHeader 給響應結果中新增一個響應頭
DedupeResponseHeader 去掉重複請求頭
Spring Cloud CircuitBreaker 斷路器
FallbackHeaders 新增熔斷後的異常資訊到請求頭
MapRequestHeader 將上游請求頭的值賦值到下游請求頭
PrefixPath 匹配的路由新增字首
PreserveHostHeader 保留原請求頭
RequestRateLimiter 限制請求的流量
RedirectTo 重定向
RemoveRequestHeader 移除請求中的一個請求頭
RemoveResponseHeader 從響應結果中移除有一個響應頭
RemoveRequestParameter 移除請求引數
RewritePath 重寫路徑
RewriteLocationResponseHeader 重寫響應頭中Location的值
RewriteResponseHeader 重寫響應頭
SaveSession 向下遊轉發請求前前置執行WebSession::save的操作
SecureHeaders 禁用預設值
SetPath 設定路徑
SetRequestHeader 重置請求頭
SetResponseHeader 修改響應頭
SetStatus 修改響應的狀態碼
StripPrefix 對指定數量的路徑字首進行去除
Retry 重試
RequestSize 請求大小大於限制時,限制請求到達下游服務
SetRequestHostHeader 重置請求頭值
Modify a Request Body 修改請求體內容
Modify a Response Body 修改響應體內容
Relay 將 OAuth2 訪問令牌向下遊轉發到它所代理的服務
CacheRequestBody 在請求正文傳送到下游之前快取請求正文並從 exchagne 屬性獲取正文

自定義區域性過濾器

自定義GatewayFilter不是直接實現GatewayFilter,而是實現AbstractGatewayFilterFactory

1、繼承AbstractGatewayFilterFactory

2、自定義名稱必須要以GatewayFilterFactory結尾並交給spring管理。

@Component
@Slf4j
public class CheckAuthGatewayFilterFactory extends AbstractGatewayFilterFactory<CheckAuthGatewayFilterFactory.Config> {
    
    public static final String KEY="key";

    public static final String VALUE="value";

    public CheckAuthGatewayFilterFactory() {
        super(CheckAuthGatewayFilterFactory.Config.class);
    }

    /**
     * 配置用於接收配置檔案中值的變數
     * @return
     */
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList(KEY, VALUE);
    }

    @Override
    public GatewayFilter apply(Config config) {

        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                ServerHttpRequest request = exchange.getRequest();
                String path = request.getURI().getRawPath();
                log.info("請求路徑:{}",path);
                log.info("{}={}",config.getKey(),config.getValue());
                return chain.filter(exchange.mutate().request(request).build());
            }
        };
    }

    /**
     *  自定義配置屬性,成員變數名稱很重要,下面會用到
     */
    @Data
    public static class Config {
        private String key;
        private String value;

    }
}

在配置檔案中可以配置

spring:
  application:
    name: gateway
  cloud:
    gateway:
      routes:
        - id: stock-service
          filters:
            - CheckAuth=age,15
          uri: lb://stock-service
          predicates:
            - Path=/stock/**

如果想要自定義過濾器在全域性起作用的話,需要把這個過濾器配置到default-filters

自定義全域性全域性過濾器

自定義全域性過濾器,直接實現GlobalFilter介面就行,而且也無法設定動態引數。Ordered是spring的核心介面,用於來對bean進行排序。

@Component
public class PrintAnyGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 編寫過濾器邏輯
        System.out.println("未登入,無法訪問");
        // 放行
        // return chain.filter(exchange);

        // 攔截
        ServerHttpResponse response = exchange.getResponse();
        response.setRawStatusCode(401);
        return response.setComplete();
    }

    @Override
    public int getOrder() {
        // 過濾器執行順序,值越小,優先順序越高
        return 0;
    }
}

常用的一些全域性過濾器如下

6.4、執行流程

  1. Gateway Client向Gateway Server傳送請求

  2. 請求首先會被HttpWebHandlerAdapter進行提取組裝成閘道器上下文

  3. 然後閘道器的上下文會傳遞到DispatcherHandler,它負責將請求分發給RoutePredicateHandlerMapping

  4. RoutePredicateHandlerMapping負責路由查詢,並根據路由斷言判斷路由是否可用

  5. 如果過斷言成功,由FilteringWebHandler建立過濾器鏈並呼叫

  6. 請求會一次經過PreFilter--微服務--PostFilter的方法,最終返回響應

如圖所示:

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

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

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

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

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

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

如圖中所示,最終請求轉發是有一個名為NettyRoutingFilter的過濾器來執行的,而且這個過濾器是整個過濾器鏈中順序最靠後的一個。如果我們能夠定義一個過濾器,在其中實現登入校驗邏輯,並且將過濾器執行順序定義到NettyRoutingFilter之前,這就符合我們的需求了!

6.5、登入實戰

登入校驗的思路:

1、登入請求先到閘道器,閘道器把登入請求轉發到user-service,user-service完成登入操作並生成token返回給前端

2、使用者請求到閘道器,閘道器對使用者請求進行校驗,判斷使用者是否登入。

(1)如果沒有登入則報錯
(2)如果登入了,則對token進行解析,然後把解析出來的使用者資訊新增到請求頭中,然後把路由轉發到業務微服務中。

3、微服務之間的呼叫是不走閘道器,此時也會有校驗,需要把請求的token也加入到服務之間呼叫的請求頭裡面

第一步使用者登入

此程式碼寫在spring-cloud-user服務裡面

/**
     * 模擬使用者登入,登入賬號為zhangsan,密碼為123456
     * @param name
     * @param password
     * @return
     * @throws Exception
     */
    @RequestMapping("/login")
    public String login(String name,String password) throws Exception {
        System.out.println("使用者名稱:"+name+",password:"+password);

        if("zhangsan".equals(name)&&"123456".equals(password)){
            String key=RedisPrefixConstants.USER_TOKEN_PREFIX+":"+name;
            String token = EncryptionUtils.encrypt(name, EncryptionUtils.SECRET_KEY);
            // 登入成功,用金鑰把登入的賬戶加密,生成的密串返回給前端(類似jwt), 並把使用者資訊存入到redis中
            redisTemplate.opsForValue().set(key,name,30, TimeUnit.MINUTES);
            return token;
        }
        throw new RuntimeException("登入失敗");

    }

第二步gateway攔截器

此程式碼寫在spring-cloud-gateway裡面

在閘道器定義一個全域性攔截器,用來校驗使用者是否登入

package com.java.coder.gateway;

import com.java.coder.common.config.AuthProperties;
import com.java.coder.common.constants.RedisPrefixConstants;
import com.java.coder.common.utils.EncryptionUtils;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
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;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

@Slf4j
@Component
@RequiredArgsConstructor
@EnableConfigurationProperties(AuthProperties.class)
public class AuthGlobalFilter implements GlobalFilter, Ordered {


    private RedisTemplate<String,Object> redisTemplate;

    private final AuthProperties authProperties;

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        // 2.判斷是否不需要攔截
        if(isExclude(request.getPath().toString())){
            // 無需攔截,直接放行
            return chain.filter(exchange);
        }
        List<String> list = request.getHeaders().get("token");
        // 既不是白名單路徑,又沒有token,則禁止訪問
        if(list==null||list.size()==0){
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.FORBIDDEN);
            return response.setComplete();

        }
        // 對token進行解密,如果解密不成功,則禁止訪問
        String token = list.get(0);
        String name = null;
        try{
            name=EncryptionUtils.decrypt(token, EncryptionUtils.SECRET_KEY);
        }catch (Exception e){
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.FORBIDDEN);
            return response.setComplete();
        }
        // 把解密出來的賬號資訊放入請求頭中
        String finalName = name;
        exchange.mutate().request(b->b.header("user-info", finalName)).build();
         return chain.filter(exchange);


    }

    @Override
    public int getOrder() {
        // 過濾器執行順序,值越小,優先順序越高
        return 0;
    }

    private boolean isExclude(String antPath) {
        for (String pathPattern : authProperties.getExcludePaths()) {
            if(antPathMatcher.match(pathPattern, antPath)){
                return true;
            }
        }
        return false;
    }
}

第三步獲取當前登入使用者的工具類

此程式碼寫在spring-cloud-common服務裡面

@Component
public class UserUtil {

    private static RedisTemplate<String,String> redisTemplate;

    @Autowired
    public UserUtil(RedisTemplate<String,String> redisTemplate){
        UserUtil.redisTemplate=redisTemplate;
    }

    public static String getCurrentUser(){
        ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = sra.getRequest();
        String name = request.getHeader("user-info");
        String key= RedisPrefixConstants.USER_TOKEN_PREFIX+":"+name;

       return redisTemplate.opsForValue().get(key);
    }
}

因此類在spring-cloud-common裡面,spring-cloud-user、spring-cloud-stock等服務無法掃描到。此時有兩種解決辦法。

第一種方式: 利用spi機制

在spring.factories裡面配置

第二種方式: 利用SpringBootApplication註解裡面的scanBasePackages來指定UserUtil所在的包的路徑

因為我們在spring-cloud-common中定義了一個配置Redis的配置類,有可能出現注入RedisTemplate衝突的情況。

此時的解決方案是加一個@AutoConfigureBefore註解

因此spring-boot-starter-data-redis中的自動配置類是RedisAutoConfiguration,這個類在注入RedisTemplate時用了一個@ConditionalOnMissingBean註解,只有當spring容器中不存在名稱為redisTemplate的Bean時才會注入。因此當我們的Redis配置先於RedisAutoConfiguration配置類執行,那麼RedisAutoConfiguration配置類就不再會注入RedisTemplate。

原文連結:https://blog.csdn.net/m0_51681531/article/details/130507029

https://blog.csdn.net/m0_51681531/article/details/130507029

第四步驗證

我們呼叫stock-service服務,如果不帶上token,且/stock/getStock不是白名單裡面的路徑,則會返回403狀態碼

http://localhost:8080/stock/getStock

如果帶上之前登入介面返回的token,則可以順利呼叫

相關文章