最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

搭建Spring cloud 非阻塞式微服務(wù)架構(gòu)的實(shí)現(xiàn)

 更新時(shí)間:2025年08月29日 09:10:26   作者:ciku  
本文介紹搭建非阻塞式微服務(wù)架構(gòu),采用Spring Gateway、Spring Security、JWT與Redis,集成EurekaServer、Auth-service、User-service等服務(wù),實(shí)現(xiàn)用戶認(rèn)證、日志跟蹤及統(tǒng)一入口管理,感興趣的可以了解一下

搭建一個(gè)非阻塞式微服務(wù)架構(gòu)

本文簡述如何搭建一個(gè)非阻塞式的微服務(wù)架構(gòu),使用Spring gateway進(jìn)行路由配置、Spring Security 進(jìn)行用戶登錄、生成JWT 令牌、token校驗(yàn),使用Redis存儲(chǔ)token信息,使用mysql數(shù)據(jù)庫存儲(chǔ)用戶信息;使用全局traceId搭配gateway、slf4j實(shí)現(xiàn)全局日志跟蹤。主要有四個(gè)子服務(wù)組成eurekaServer、gateway、auth-service、user-service。eurekaServer負(fù)責(zé)服務(wù)注冊(cè)與發(fā)現(xiàn)、gateway負(fù)責(zé)統(tǒng)一的入口配置、auth-service 負(fù)責(zé)登錄、認(rèn)證;user-service用戶管理。

版本介紹:

  • Spring Boot: 3.5.5
  • JDK:17.0.12
  • Spring Cloud版本:2025.0.0
  • Spring Boot和Spring cloud依賴關(guān)系如下:

父工程配置

pom.xml
引入基本的webflux、devtools、數(shù)據(jù)庫連接工具、ribbon與OpenFeign依賴、spring colud管理包、定義子服務(wù)等。

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>TraceLogDemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>TraceLogDemo</name>
    <description>TraceLogDemo</description>
    <packaging>pom</packaging> <!-- 不打包成jar-->
    <modules><!-- 子服務(wù)-->
        <module>eurekaServer</module>
        <module>gateway</module>
        <module>user-service</module>
        <module>auth-service</module>
    </modules>
    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2025.0.0</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

搭建eurekaServer

1.pom.xml
pom中修改parent為父工程的groupId、artifactid等信息,引入spring-cloud-starter-netflix-eureka-server依賴。

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>com.example</groupId>
		<artifactId>TraceLogDemo</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>
	<artifactId>eurekaServer</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>eurekaServer</name>
	<description>eurekaServer</description>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<!--eureka Server依賴-->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

2.修改配置文件

spring:
  application:
   name: eurekaServer
server:
  port: 8761
eureka:
  client:
    register-with-eureka: false #是否注冊(cè)為客戶端
    fetch-registry: false 

3.修改啟動(dòng)類

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

	public static void main(String[] args) {
		SpringApplication.run(EurekaServerApplication.class, args);
	}

}

啟動(dòng)后訪問locahost:8761,顯示如下界面:

搭建Auth-Service認(rèn)證服務(wù)

1.pom.xml
核心引入Spring security和jwt依賴

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>com.example</groupId>
		<artifactId>TraceLogDemo</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>
	<artifactId>auth-service</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>auth-service</name>
	<description>auth-service</description>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>
		<!--spring security依賴-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
			<exclusions>
				<exclusion>
					<groupId>org.springframework.boot</groupId>
					<artifactId>spring-boot-starter-web</artifactId>
				</exclusion>
				<exclusion>
					<groupId>org.springframework.cloud</groupId>
					<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>jakarta.servlet</groupId>
			<artifactId>jakarta.servlet-api</artifactId>
			<version>6.0.0</version>
			<scope>provided</scope>
		</dependency>
		<!--引入jwt依賴-->
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-api</artifactId>
			<version>0.11.5</version>
		</dependency>
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-impl</artifactId>
			<version>0.11.5</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-jackson</artifactId>
			<version>0.11.5</version>
			<scope>runtime</scope>
		</dependency>
		<!--mybatis plus依賴-->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
			<version>3.5.10</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
		<!--redis依賴-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

2.修改配置文件
數(shù)據(jù)庫配置、redis配置、mybatis plus配置、eureka注冊(cè)

spring:
  application:
    name: auth-service
  datasource:
    url: jdbc:mysql://192.168.48.164:3306/db01?severTimezone=UTC
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  main:
    allow-bean-definition-overriding: true
  autoconfigure:
    exclude: org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
  data:
    redis:
      host: 192.168.48.164
      port: 6379
      password: 123456
      database: 10
server:
  port: 8082
mybatis-plus:
  mapper-locations: classpath*:mapper/**/*.xml
  type-aliases-package: com.example.entity
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

3.業(yè)務(wù)代碼
由于此處代碼很多省略基礎(chǔ)查詢代碼以及redis相關(guān)代碼。只給核心代碼
SecurityConfig.java:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import reactor.core.publisher.Mono;

/**
 * @ClassName:SecurityConfig
 * @Author: xuli
 * @Date: 2025/8/26 18:22
 * @Description: 必須描述類做什么事情, 實(shí)現(xiàn)什么功能
 */
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        return http
                .csrf(ServerHttpSecurity.CsrfSpec::disable)//禁用csrf
                .formLogin(ServerHttpSecurity.FormLoginSpec::disable)//禁用表單登錄
                .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)//禁用基礎(chǔ)登錄
                .authorizeExchange(exchanges -> exchanges
                        .pathMatchers("/auth/**","/user/**").permitAll()//允許/auth/**所有的
                        .anyExchange().authenticated()//其他都要授權(quán)
                )
                .exceptionHandling(handling -> handling
                        .authenticationEntryPoint((exchange, ex) ->
                                Mono.fromRunnable(() -> {//設(shè)置異常狀態(tài)
                                    exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                                }))
                        .accessDeniedHandler((exchange, denied) ->
                                Mono.fromRunnable(() -> {
                                    exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
                                }))
                )
                .build();
    }
    /**
     * 響應(yīng)式認(rèn)證管理器(核心組件)
     * @param userDetailsService 實(shí)現(xiàn)ReactiveUserDetailsService接口的用戶服務(wù)
     * @param passwordEncoder 已配置的密碼編碼器
     * @return 認(rèn)證處理鏈,完成憑證驗(yàn)證和權(quán)限注入
     */
    @Bean
    public ReactiveAuthenticationManager AuthenticationManager(
            ReactiveUserDetailsService userDetailsService,
            PasswordEncoder passwordEncoder) {

        return authentication -> {//調(diào)用userDetailsService實(shí)現(xiàn)校驗(yàn)
            UsernamePasswordAuthenticationToken authToken =
                    (UsernamePasswordAuthenticationToken) authentication;
            return userDetailsService.findByUsername(authToken.getName())
                    .filter(user -> passwordEncoder.matches(
                            authToken.getCredentials().toString(),
                            user.getPassword()
                    ))
                    .switchIfEmpty(Mono.error(new BadCredentialsException("Invalid credentials")))
                    .map(user -> new UsernamePasswordAuthenticationToken(
                            user.getUsername(),
                            user.getPassword(),
                            user.getAuthorities()
                    ));
        };
    }
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return web -> web.debug(true);
    }
    /**
     * BCrypt密碼編碼器(安全必須)
     * @return 強(qiáng)度10的BCrypt實(shí)現(xiàn)
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Bean
    public TraceWebFilter TraceWebFilter() {
        return new TraceWebFilter();
    }
    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        return new JwtAuthenticationFilter();
    }


}

全局日志攔截過濾器:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

/**
 * @ClassName:TraceWebFilter
 * @Author: ciku
 * @Date: 2025/8/28 15:54
 * @Description: 全局traceFilter,用于從request中獲取traceId設(shè)置在MDC中
 */
@Component
public class TraceWebFilter implements WebFilter {
    private static final String TRACE_ID_HEADER = "X-Trace-Id";
    private Logger logger= LoggerFactory.getLogger(TraceWebFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        return Mono.defer(()->{
            String traceId = exchange.getRequest().getHeaders().getFirst(TRACE_ID_HEADER);//獲取header里面的traceId
            if(!traceId.isBlank()){
                MDC.put("traceId",traceId);
            }
            return chain.filter(exchange);

        });
    }
}

JwtUtil:

package com.example.authservice.util;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.Date;
/**
 * @ClassName:JwtUtil
 * @Author: xuli
 * @Date: 2025/8/26 21:48
 * @Description: 必須描述類做什么事情, 實(shí)現(xiàn)什么功能
 **/
@Component
public class JwtUtil {
    private static final String SECRET_KEY = "u/4W2VX5J6QbLdE9NfG3BcH7YzA1kDmP8sR0tIw=";
    private static final long EXPIRATION_TIME = 86400000; // 24小時(shí)
    private static Logger logger= LoggerFactory.getLogger(JwtUtil.class);

    /**
     * 根據(jù)用戶名生成token
     * @param username
     * @return
     */
    public static String generateToken(String username) {
        String token = Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .signWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()))
                .compact();
        logger.info("生成token成功!token:"+token);
        return token;
    }

}

獲取用戶對(duì)象,實(shí)現(xiàn)ReactiveUserDetailsService 接口是Spring security提供的SPI接口。

import com.example.authservice.user.entity.User;
import com.example.authservice.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.util.ArrayList;

/**
 * @ClassName:UserDetailsServiceImpl
 * @Author: xuli
 * @Date: 2025/8/26 22:20
 * @Description: 實(shí)現(xiàn)ReactiveUserDetailsService的方法,進(jìn)行用戶名密碼校驗(yàn)
 */

@Service
public class UserDetailsServiceImpl implements ReactiveUserDetailsService {
    @Autowired
    private UserService userService;

    @Override
    public Mono<UserDetails> findByUsername(String username) {
        return Mono.just(loadUserByUsername(username));
    }
    private UserDetails loadUserByUsername(String username) {
        User user = userService.findUserByUsername(username);//查詢數(shù)據(jù)庫的用戶信息
        if(user==null){
            throw new UsernameNotFoundException("user not found");
        }
        return new org.springframework.security.core.userdetails.User(user.getUserName(),user.getPassword(),
                new ArrayList<>()
        );
    }
}

所有業(yè)務(wù)自服務(wù)需要增加 logback-spring.xml,后續(xù)不再重復(fù):

<configuration>
    <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] [%X{traceId}] %-5level %logger{36} - %msg%n"/>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

搭建Gateway網(wǎng)關(guān)服務(wù)

1.pom.xml

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>TraceLogDemo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <artifactId>gateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gateway</name>
    <description>gateway</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <!--webflux對(duì)應(yīng)的gateway依賴-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway-server-webflux</artifactId>
        </dependency>
        <!--基于netflix的eureka client依賴-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2.配置文件
注冊(cè)到 http://localhost:8761 eureka服務(wù)端

spring:
  application:
   name: gateway
server:
  port: 8080
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
    fetch-registry: true
    register-with-eureka: true

3.啟動(dòng)類

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient  //標(biāo)記為客戶端
public class GatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }

}

4.配置路由信息

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;



/**
 * @ClassName:BeanConfig
 * @Author: xuli
 * @Date: 2025/8/26 16:55
 * @Description: 配置網(wǎng)關(guān)路由
 */
@Configuration
public class RuteConfiguration {
    @Bean
    public RouteLocator myRoutes(RouteLocatorBuilder builder){
        return builder.routes()
                .route("user-service",p->p.path("/userService/**")//設(shè)置路由,以u(píng)serService開頭的都調(diào)用user-service服務(wù)
                        .filters(f->f.stripPrefix(1))
                        .uri("lb://user-service"))
                .route("auth-service",p->p.path("/authService/**")//設(shè)置路由,以authService開頭的都調(diào)用auth-service服務(wù)
                        .filters(f->f.stripPrefix(1))
                        .uri("lb://auth-service")//默認(rèn)規(guī)則lb://服務(wù)名,eureka大小寫不限,nacos小大寫區(qū)分
                )
                .build();
    }

}

5.定義jwt校驗(yàn)返回對(duì)象

package com.example.gateway.config;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;

/**
 * @ClassName:JwtResponse
 * @Author: xuli
 * @Date: 2025/8/26 22:56
 * @Description: jwt校驗(yàn)返回對(duì)象
 */
@Data
@JsonInclude
public class JwtResponse {
    private String token;
    private boolean isExpiration;
    private String error;

    // 必須有無參構(gòu)造函數(shù)
    public JwtResponse() {}

    public JwtResponse(String token, String error) {
        this.token = token;
        this.error=error;
    }

    public JwtResponse(String token, boolean isExpiration, String error) {
        this.token = token;
        this.isExpiration = isExpiration;
        this.error = error;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public boolean isExpiration() {
        return isExpiration;
    }

    public void setExpiration(boolean expiration) {
        isExpiration = expiration;
    }

    public String getError() {
        return error;
    }

    public void setError(String error) {
        this.error = error;
    }
}

6.配置過濾器
用于請(qǐng)求攔截,獲取jwt 令牌,再調(diào)用auth-service校驗(yàn),并設(shè)置全局traceId

package com.example.gateway.filter;

import com.example.gateway.config.JwtResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import reactor.util.retry.Retry;

import java.time.Duration;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

/**
 * @ClassName:JwtAuthFilter
 * @Author: xuli
 * @Date: 2025/8/27 23:50
 * @Description: JWT過濾器,接口調(diào)用是校驗(yàn)token并設(shè)置traceId
 */
@Component
public class JwtAuthFilter implements GlobalFilter {
    private final ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory;
    private static final Logger logger = LoggerFactory.getLogger(JwtAuthFilter.class);
    private final static String TRACE_HEADER="X-Trace-Id";

    private final WebClient webClient;
    public JwtAuthFilter(ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory
            ,WebClient.Builder webClientBuilder,LoadBalancerClient loadBalancerClient){
        this.loadBalancerFactory = loadBalancerFactory;
        this.webClient = webClientBuilder
                .clientConnector(new ReactorClientHttpConnector(
                        HttpClient.create()
                                .responseTimeout(Duration.ofSeconds(10))
                                .keepAlive(true)
                )).build();
    }
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    ReactiveLoadBalancer<ServiceInstance> loadBalancer =  loadBalancerFactory.getInstance("auth-service");//獲取實(shí)際地址
    ServerHttpRequest request = exchange.getRequest();
    String path = exchange.getRequest().getURI().getPath();
    if("/auth/login".equals(path)){//如果是登錄接口直接放行
        //增加全局traceId
        String  traceId= UUID.randomUUID().toString();
        MDC.put("traceId",traceId);
        //traceId設(shè)置在請(qǐng)求頭中
        ServerHttpRequest mutatedRequest = exchange.getRequest()
                .mutate()
                .header("X-Trace-Id",traceId)
                .build();
        return chain.filter(exchange.mutate().request(mutatedRequest).build());
    }
    String loginName = extractLoginName(request);
    String token = extractToken(request);
    return Flux.from(loadBalancer.choose())
            .take(1)
            .single()
            .flatMap(response->{
                ServiceInstance instance = response.getServer();//獲取返回的實(shí)例
                if(instance!=null){
                    logger.info("獲取到實(shí)例地址"+instance.getUri());
                }
                //生成traceId
                String  traceId= UUID.randomUUID().toString();
                MDC.put("traceId",traceId);

                //調(diào)用校驗(yàn)token接口
                return webClient.get()
                        .uri( instance.getUri()+"/auth/validateToken")
                        .header(HttpHeaders.AUTHORIZATION,token)
                        .header("X-User-Id",loginName)
                        .header("Content-Type","application/json")
                        .header("X-Trace-Id",traceId)
                        .retrieve()
                        .bodyToMono(JwtResponse.class)//設(shè)置返回類型,與接口返回類型一直
                        .flatMap(jwtResponse -> {
                            if(!jwtResponse.isExpiration()){//獲取返回結(jié)果
                                logger.info("接口校驗(yàn)失敗,返回"+jwtResponse.isExpiration()+"請(qǐng)確認(rèn)token是否過期");
                            }
                            if (jwtResponse.isExpiration()) {//校驗(yàn)通過,調(diào)用請(qǐng)求的接口
                                ServerHttpRequest mutatedRequest = exchange.getRequest()
                                        .mutate()
                                        .header("X-User-Id", loginName)
                                        .header("Authorization")
                                        .header("X-Trace-Id",traceId)
                                        .build();
                                logger.info(instance.getUri()+"/auth/validateToken"+"校驗(yàn)成功");
                                return chain.filter(exchange.mutate().request(mutatedRequest).build());
                            }
                            return Mono.error(new ResponseStatusException(HttpStatus.UNAUTHORIZED));//失敗返回401
                        });
            })
            .onErrorResume(e -> {//異常處理
                 logger.error(e.getMessage());
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
            });

}

    /**
     * 未授權(quán)設(shè)置401
     * @param exchange
     * @param error
     * @return
     */
    private Mono<Void> unauthorized(ServerWebExchange exchange, String error) {
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        return exchange.getResponse().setComplete();
    }

    /**
     * 獲取token
     * @param request
     * @return
     */
    private String extractToken(ServerHttpRequest request) {
        HttpHeaders headers = request.getHeaders();
        List<String> list = headers.get("Authorization");
        if(!CollectionUtils.isEmpty(list)){
            String string = list.get(0);
            return !string.isBlank()?string.replaceAll("Bearer ", ""):"";
        }
        return "";
    }

    /**
     * 獲取登錄名
     * @param request
     * @return
     */
    private String extractLoginName(ServerHttpRequest request) {
        HttpHeaders headers = request.getHeaders();
        List<String> list = headers.get("X-User-Id");
        if(!CollectionUtils.isEmpty(list)){
            String string = list.get(0);
            return !string.isBlank()?string:"";
        }
        return "";
    }

}

搭建業(yè)務(wù)服務(wù) UserSevice

userService簡單實(shí)現(xiàn),僅供演示
1.pom.xml
只需引入eureka-client

 <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

2.全局trace過濾器

@Component
public class TraceWebFilter implements WebFilter {
    private static final String TRACE_ID_HEADER = "X-Trace-Id";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        return Mono.defer(()->{

            String traceId = exchange.getRequest().getHeaders().getFirst(TRACE_ID_HEADER);
            return chain.filter(exchange)
                    .contextWrite(ctx->ctx.put("traceId", traceId))
                    .doOnEach(sinal->{
                        if(sinal.isOnComplete()){
                            MDC.put("traceId",traceId);//設(shè)置在slf4j的日志對(duì)象中
                        }
                    });

        });
    }
}

3.controller簡單實(shí)現(xiàn)

@RestController
@RequestMapping("/user")
public class TestController {
    private Logger logger= LoggerFactory.getLogger(TestController.class);

    @GetMapping("/sayHello")
    public String sayHello(){
        //todo
        logger.info("調(diào)用user-service/user/sayHello接口成功!");
        return "welcome User service";
    }
}

配置文件就不再贅述,只需要注冊(cè)到eurake中即可。

運(yùn)行結(jié)果

1.未登錄調(diào)用userService

2.全局日志跟蹤

3.用戶登錄成功

采用統(tǒng)一入口調(diào)用登錄方法,成功返回token,并記錄在redis中

4.攜帶最新token訪問user-Service

5.失效token或錯(cuò)誤token

到此這篇關(guān)于搭建Spring cloud 非阻塞式微服務(wù)架構(gòu)的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Spring cloud 非阻塞式微服務(wù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • springcloud?gateway實(shí)現(xiàn)簡易版灰度路由步驟詳解

    springcloud?gateway實(shí)現(xiàn)簡易版灰度路由步驟詳解

    這篇文章主要為大家介紹了springcloud?gateway實(shí)現(xiàn)簡易版灰度路由步驟詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-11-11
  • 關(guān)于@SpringBootApplication詳解

    關(guān)于@SpringBootApplication詳解

    這篇文章主要介紹了關(guān)于@SpringBootApplication的使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • Java tomcat手動(dòng)配置servlet詳解

    Java tomcat手動(dòng)配置servlet詳解

    這篇文章主要為大家介紹了tomcat手動(dòng)配置servlet,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2021-11-11
  • ConcurrentHashMap原理及使用詳解

    ConcurrentHashMap原理及使用詳解

    ConcurrentHashMap是Java中的一種線程安全的哈希表實(shí)現(xiàn),它提供了與Hashtable和HashMap類似的API,是一個(gè)高效且可靠的多線程環(huán)境下的哈希表實(shí)現(xiàn),非常適合在并發(fā)場景中使用,本文就簡單介紹一下ConcurrentHashMap原理及使用,需要的朋友可以參考下
    2023-06-06
  • Java中的AQS入門攻略

    Java中的AQS入門攻略

    AQS(AbstractQueuedSynchronizer)是JUC包中用于構(gòu)建鎖和同步器的核心框架,它通過volatile的state變量和FIFO隊(duì)列實(shí)現(xiàn)線程同步,簡化了同步工具的開發(fā),并支持獨(dú)占與共享兩種模式,本文介紹Java中的AQS是什么,感興趣的朋友跟隨小編一起看看吧
    2025-10-10
  • JavaWeb學(xué)習(xí)教程之Web服務(wù)器、Tomcat服務(wù)器、ASP、PHP、JSP

    JavaWeb學(xué)習(xí)教程之Web服務(wù)器、Tomcat服務(wù)器、ASP、PHP、JSP

    JavaWeb開發(fā)是一個(gè)重要的領(lǐng)域,而Tomcat服務(wù)器是JavaWeb應(yīng)用程序常用的運(yùn)行環(huán)境,這篇文章主要介紹了JavaWeb學(xué)習(xí)教程之Web服務(wù)器、Tomcat服務(wù)器、ASP、PHP、JSP的相關(guān)資料,需要的朋友可以參考下
    2026-03-03
  • Java傳引用問題舉例詳解

    Java傳引用問題舉例詳解

    引用傳遞是Java中一個(gè)重要的概念,它與值傳遞不同,可以使得函數(shù)或方法可以修改傳入的對(duì)象,這篇文章主要給大家介紹了關(guān)于Java傳引用問題的相關(guān)資料,需要的朋友可以參考下
    2024-07-07
  • Java集合Map常見問題_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    Java集合Map常見問題_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    這篇文章主要為大家詳細(xì)整理了Java集合Map常見問題,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • Java設(shè)計(jì)模式之觀察者模式_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    Java設(shè)計(jì)模式之觀察者模式_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    這篇文章給大家介紹流量java設(shè)計(jì)模式之觀察者模式,定義對(duì)象間一種一對(duì)多的依賴關(guān)系,使得當(dāng)每一個(gè)對(duì)象改變狀態(tài)。下面通過類圖和實(shí)例代碼給大家介紹java設(shè)計(jì)模式之觀察者模式,感興趣的朋友一起看看吧
    2017-08-08
  • Java安全之Filter權(quán)限繞過的實(shí)現(xiàn)

    Java安全之Filter權(quán)限繞過的實(shí)現(xiàn)

    在一些需要挖掘一些無條件RCE中,大部分類似于一些系統(tǒng)大部分地方都做了權(quán)限控制的,而這時(shí)候想要利用權(quán)限繞過就顯得格外重要,本文就介紹了如何實(shí)現(xiàn),一起來了解一下
    2021-05-05

最新評(píng)論

防城港市| 阿合奇县| 巍山| 进贤县| 招远市| 玉屏| 珲春市| 吴川市| 城固县| 沂南县| 通渭县| 海晏县| 宜春市| 常山县| 永春县| 浦江县| 南木林县| 怀安县| 苏尼特左旗| 余庆县| 景泰县| 从江县| 郯城县| 工布江达县| 昆山市| 金平| 通河县| 革吉县| 海阳市| 南岸区| 陆河县| 长乐市| 武乡县| 尚志市| 莲花县| 四会市| 连州市| 清苑县| 涪陵区| 富平县| 富阳市|