springCloud/Alibaba常用中间件之GateWay网关

文章目录


SpringCloud:

微服务的中间件介绍与使用

微服务架构体系图:

依赖版本补充

下面所有代码中的依赖版本如下:

xml 复制代码
<properties>
  <maven.compiler.source>17</maven.compiler.source>
  <maven.compiler.target>17</maven.compiler.target>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <hutool.version>5.8.22</hutool.version>
  <lombok.version>1.18.30</lombok.version>
  <druid.version>1.2.18</druid.version>
  <mybatis.springboot.version>3.0.3</mybatis.springboot.version>
  <mysql.version>8.0.33</mysql.version>
  <fastjson2.version>2.0.48</fastjson2.version>
  <swagger3.version>2.2.0</swagger3.version>
  <mapper.version>4.2.3</mapper.version>
  <persistence-api.version>1.0.2</persistence-api.version>
  <spring.boot.test.version>3.1.5</spring.boot.test.version>
  <spring.boot.version>3.2.0</spring.boot.version>
  <spring.cloud.version>2023.0.0</spring.cloud.version>
  <spring.cloud.alibaba.version>2023.0.0.0-RC1</spring.cloud.alibaba.version>
  <knife4j-openapi3.version>4.4.0</knife4j-openapi3.version>
</properties>

GateWay:网关

什么是网关网关 简单的理解就是在请求和服务之间加了一个 中间层
作用是

一、管控 路由的请求(反向代理、鉴权、流量控制、熔断、日志监控)

二、在每次路由中进行 增删内容

图形化展示网关位置:


工作原理

简单总结就是:路由转发+断言判断+执行过滤链列表

参考官网:https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/how-it-works.html

三大核心之Router:路由

概念:路由是构建网管的基本模块,它有ID、目标URI、一系列断言和过滤器组成,如果断言为true则匹配该条路由

接下来直接开始配置一个基本的路由

1、导入基础依赖
html 复制代码
<!--gateway依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--服务注册发现consul discovery,网关也要注册进服务注册中心统一管控-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- 指标监控健康检查的actuator,网关是响应式编程删除掉spring-boot-starter-web dependency-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2、进行服务注册
yml 复制代码
server:
  port: 9567
spring:
  application:
    name: cloud-gateway
  cloud:
    consul:
      port: 8500 #consul的端口号
      host: localhost #consul的网址
      discovery:
        service-name: ${spring.application.name} #配置服务名

启动类添加注册注解

java 复制代码
@SpringBootApplication
@EnableDiscoveryClient/*服务组注册*/
public class Main9567 {
    public static void main(String[] args) {
        SpringApplication.run(Main9567.class, args);
    }
}

启动查看是否完成了服务注册

3、路由映射

这里可以先创建一个简单的业务[可以不需要数据库就返回一个值即可]做映射地址(别忘了要进行服务注册)

这里为了方便我就修改一下上面的业务的进行测试了。配置好测试业务之后,在路由服务的配置文件中进行配置:

yml 复制代码
server:
  port: 9567

spring:
  application:
    name: cloud-gateway
  cloud:
    consul:
      port: 8500 #consul的端口号
      host: localhost #consul的网址
      discovery:
        service-name: ${spring.application.name} #配置服务名

    gateway:
      routes:
        - id: gateway_router_1  #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
          #配置地址方法一(不推荐):
          #          uri: localhost:8001 #填写业务服务的地址和端口号(缺点:灵活性差)
          #方法二(推荐):动态路由
          uri: lb://cloud-payment-service #填写业务服务名(优点:可动态的配置路由,lb:可以支持负载均衡)
          predicates: # 配置一个简单的断言,以便于演示。也就是访问此路由的条件
            - Path=/pay/gateway/get/** #满足此路由的才可以访问,**:通配符
        - id: gateway_router_2
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/info/**
4、测试访问GateWay的端口是否可以访问

访问成功,说明映射成功了!

这里可以也试一下当有多个服务(yourServerName),下使用lb://yourServerName是否有负载均衡的效果

三大核心之Predicate:断言

概念:开发人员可以匹配HTTP请求中的所有内容,如果请求与断言相匹配则进行路由

配置文件
yml 复制代码
    gateway:
      routes:
        - id: gateway_router_1  #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
          #配置地址方法一(不推荐):
          #          uri: localhost:8001 #填写业务服务的地址和端口号(缺点:灵活性差)
          #方法二(推荐):动态路由
          uri: lb://cloud-payment-service #填写业务服务名(优点:可动态的配置路由,lb:可以支持负载均衡)
          predicates: # 配置一个简单的断言,以便于演示。也就是访问此路由的条件
            - Path=/pay/gateway/get/** #满足此路由的才可以访问,**:通配符
        - id: gateway_router_2
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由
            - After=2025-04-20T16:04:22.672173200+08:00[Asia/Shanghai]   #配置指定的时间之后才开启此路由
            - Before=2025-04-20T16:08:13.979939200+08:00[Asia/Shanghai]   #与after相反,没有达到此时间之后都可以访问,过了之后便会直接关闭
            - Between=2025-04-20T16:09:38.676463500+08:00[Asia/Shanghai],2025-04-20T16:11:38.676463500+08:00[Asia/Shanghai] #区间
            - Cookie=phone,^1(3[0-9]|4[57]|5[0-35-9]|7[0678]|8[0-9])\d{8}$    #根据cookie参数进行验证,第一个是key值,第二个是规定value值正则
            - Header=X-Request-Id, \d+    #根据请求头的参数名和值进行判断,第一个值是name,第二个值是正则表达式
            - Host=**.chyb.com,**.chyb.org  #根据Host这个参数进行模糊查询,**:通配符
            - Query=num, \d+ #必须要有num这个参数,且值为整数
            - RemoteAddr=192.168.235.0/24 #根据请求人的ip进行拦截,/24:是指前面的24位是不可以变的,24:前面的192.168.43是不可以变得,注意:这里请求的时候不要在使用localhost而是要输入自己的IP地址
            - Method=GET,POST #按照请求方式进行匹配

这里就相当于API的调用一样没有什么好说的,不清楚的可以直接参考官方文档:https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/request-predicates-factories.html

中文官网:https://springdoc.cn/spring-cloud-gateway/#gateway-request-predicates-factories

这里在单独将一个知识点领出来说:快捷方式的配置(常用)完全展开的参数 语法如下:

yml 复制代码
# 快捷方式的配置
- Cookie=mycookie,mycookievalue
#完全展开的参数:完全展开的参数看起来更像标准的yaml配置,有名称/值对。一般来说,会有一个 name key和一个 args key。args key是一个键值对的映射,用于配置谓词或过滤器。
- name: Cookie
  args:
    name: mycookie
    regexp: mycookievalue
自定义Predicate(断言)

创建 ~RoutePredicateFactory 类,其代码如下:

java 复制代码
@Component /*注入Spring容器中*/
/*注意命名规范:~~~RoutePredicateFactory*/
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {
    public MyRoutePredicateFactory() {
        super(MyRoutePredicateFactory.Config.class);
    }

    @Validated
    public static class Config {
        @Setter
        @Getter
        @NotEmpty
        private String userType; //钻、金、银等用户等级
    }

    /**
     * 写逻辑:根据userType进行判断是否可以访问此路由
     * @param config
     * @return
     */
    @Override
    public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config) {
        return new Predicate<ServerWebExchange>() {
            @Override
            public boolean test(ServerWebExchange serverWebExchange) {
                //检查request的参数里面,userType是否为指定的值,符合配置就通过
                String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");

                if (userType == null) return false;

                //如果说参数存在,就和config的数据进行比较
                if (userType.equals(config.getUserType())) {
                    return true;
                }

                return false;
            }
        };
    }

    /**
     * 用来支持断言的快捷方式
     * @return
     */
    @Override
    public List<String> shortcutFieldOrder() {
        return Collections.singletonList("userType");
    }
}

配置文件的设置

yml 复制代码
#....
predicates:
  #这里的"My"是根据你类名有关系,当遵守命名规范:~RoutePredicateFactory,则默认的名字就是:~
  - My=diamond  #当参数userType的值等于diamond才可以访问此路由

三大核心之Filter:过滤

概念:指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求路由之前或之后对请求进行修改

过滤器类型分为:

  1. 全局默认过滤器 (Global Filters):每一个路由都要执行的过滤器
  2. 单一内置过滤器 (GateWayFilter),"又称路由过滤器":指定某一个路由执行的过滤器
配置文件
yml 复制代码
          filters:
            #             RequestHeader的增删改
            - AddRequestHeader=chyb-name, chybName1 #添加一个请求头(chyb-name)值为:chybName1
            - AddRequestHeader=chyb-name2, chybName2
            - RemoveRequestHeader=chyb-name  #删除一个请求头(user-agent)
            - SetRequestHeader=chyb-name2, chybName3 #修改某一个请求头(chyb-name2)的值为chybName3,若是没哟此请求头,则直接会自动添加

            #             RequestParameter请求参数的增删
            - AddRequestParameter=param1, 2983 #增加参数
            - RemoveRequestParameter=param2   #删除参数

            #             ResponseHeader 响应头的增删改
            - AddResponseHeader=chybName, chyb1 #创建一个响应头(chybName)值为:chyb1
            - SetResponseHeader=date, 2006-02-09 #设置date响应头的值为:2006-02-09
            - RemoveResponseHeader=Content-Type #删除Content-Type(接受类型)

            #             前缀、路径、相关的配置
            - PrefixPath=/pay   #设置访问路径的前缀
            - SetPath=/pay/gateway/{param}   #设置访问路径,{~}为占位符
            - RedirectTo=301, https://baidu.com   #重定向到https://baidu.com,状态吗为302

参考官网:https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/gatewayfilter-factories.html

中文官网:https://springdoc.cn/spring-cloud-gateway/#gatewayfilter-工厂

自定义全局过滤 ------ 输出每一个路由所执行的时间

1、创建一个~~GlobalFilter,并实现自定义过滤器所需的接口:GlobalFilter, Ordered

java 复制代码
/*注入到Spring容器中,之后会将所有注入到容器中的过滤器进行合并,形成过滤器列表*/
@Component
@Slf4j
/*这里是自定配置全局过滤器,就是每一个路由都会去执行的*/
public class MyGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return null;
    }

    /**
     * 设置执行接口进行排序,返回值越小越靠前
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

2、在filter方法下写执行逻辑,实现:输出每一个路由所执行的时间

java 复制代码
public static final String BEGIN_VISIT_TIME = "BEGIN_VISIT_TIME";/*初始化方法执行的开始时间*/

/**
 *用来接受响应请求报文,以及控制放行操作(这里有点像将拦截器中的preHandle[访问前拦截方法]和postHandle[访问后拦截方法]结合到一起了)
 * @param exchange :类似于Server,它里面封装了三个属性:
 *                 ServerHttpResponse、ServerHttpRequest:这两个属性存储着:请求/响应的报文,所以可以通过exchange这个参数获取到这些属性
 *                 Map<String, Object>:这属性就是我们要进行额外的添加对象就可以添加到这里,运用的方法就直接.pus即可
 * @param chain : 这个属性可以控制过滤器的放行操作
 * @return 这里返回的类型是Mono这个类型是在SpringBoot响应式编程中所引出的类,适用于高并发和高吞吐量场景(在资源有限的情况下提高系统的吞吐量和伸缩性【并非提高性能】)
 */
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    System.out.println("=====" + exchange.getAttributes().get(BEGIN_VISIT_TIME) + "=====");
    exchange.getAttributes().put(BEGIN_VISIT_TIME, System.currentTimeMillis());
    System.out.println("------" + exchange.getAttributes().get(BEGIN_VISIT_TIME) + "-----");

    /**
     * 这里的chain.filter(exchange)是放行操作,而.then()是任务串联(在 Mono完成后触发新任务,但不依赖前一个任务的输出结果。)
     */
    return chain.filter(exchange).then(Mono.fromRunnable(() -> {
        Long beginTime = exchange.getAttribute(BEGIN_VISIT_TIME);
        URI uri = exchange.getRequest().getURI();
        if (beginTime != null) {
            log.info("访问接口主机:{}", uri.getHost());
            log.info("访问接口端口号:{}", uri.getPort());
            log.info("放文件接口URL:{}", uri.getPath());
            log.info("访问接口的URL参数:{}", uri.getQuery());
            log.info("访问时长:{}ms", (System.currentTimeMillis() - beginTime));
            log.info("============================================");
            System.out.println();
        }
    }));
}

这里因为是全局过滤器,所以并不需要进行单独配置yml。
3、测试(访问任意路由查看日志是否输出)

自定义路由过滤器 ------ 输出指定的路由所执行的时间、设置路由必填参数

1、创建自定义路由过滤器类,注意类名~~GatewayFilterFactory

案例一:日志输出指定的路由所执行的时间

java 复制代码
/*注入到Spring容器中,之后会将所有注入到容器中的过滤器进行合并,形成过滤器列表*/
@Component
@Slf4j
/*自定义路由过滤器,指定某一个路由执行的过滤器,无参数的写法(只需要编写apply方法即可,注意config的类型)*/
/*注意命名规范:~GatewayFilterFactory*/
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
    static final String BEGIN_TIME = "begin_time";

    @Override
    public GatewayFilter apply(Object config) {
        System.out.println("进入了自定义网关过滤器MyGatewayFilterFactory,status:" + config);
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                exchange.getAttributes().put(BEGIN_TIME, System.currentTimeMillis());
                return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                    Long beginTime = exchange.getAttribute(BEGIN_TIME);
                    URI uri = exchange.getRequest().getURI();
                    if (beginTime != null) {
                        log.info("------------------我是路由过滤器----------------------");
                        log.info("访问接口主机:{}", uri.getHost());
                        log.info("访问接口端口号:{}", uri.getPort());
                        log.info("放文件接口URL:{}", uri.getPath());
                        log.info("访问接口的URL参数:{}", uri.getQuery());
                        log.info("访问时长:{}ms", (System.currentTimeMillis() - beginTime));
                        log.info("============================================");
                        System.out.println();
                    }
                }));
            }
        };
    }
}

案例二:设置路由必填参数

java 复制代码
@Component
/*定义路由有参过滤器*/
public class MustQueryGatewayFilterFactory extends AbstractGatewayFilterFactory<MustQueryGatewayFilterFactory.Config> {
    /*初始化*/
    public MustQueryGatewayFilterFactory() {
        super(MustQueryGatewayFilterFactory.Config.class);
    }

    /*用来配置快捷访问方式的传参的位置*/
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("status");
    }

    /*定义一个内部类用来存放接受的参数,同时也是继承AbstractGatewayFilterFactory类的泛型*/
    public static class Config {
        @Getter
        @Setter
        private String status;//设定一个状态值/标志位,它等于多少,匹配和才可以访问
    }

    /**
     * 写过滤器逻辑的方法
     * @param config 传递参数值
     * @return
     */
    @Override
    public GatewayFilter apply(MustQueryGatewayFilterFactory.Config config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                ServerHttpRequest request = exchange.getRequest();
                /*动态的将配置的参数设为传必传参数的name*/
                System.out.println("进入了自定义网关过滤器MyGatewayFilterFactory,status:" + config.getStatus());
                if (request.getQueryParams().containsKey(config.getStatus())) {
                    return chain.filter(exchange);
                } else {
                    exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
                    return exchange.getResponse().setComplete();
                }
            }
        };
    }
}
//单一内置过滤器GatewayFilter

2、配置yml

在指定的路由上添加路由过滤器

yml 复制代码
server:
  port: 9567
spring:
  application:
    name: cloud-gateway
  cloud:
    consul:
      port: 8500 #consul的端口号
      host: localhost #consul的网址
      discovery:
        service-name: ${spring.application.name} #配置服务名
    gateway:
      routes:
        - id: gateway_router_1
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/info/**              # 断言,路径相匹配的进行路由
          filters:
            - My #配置日志输出指定的路由所执行的时间的路由过滤器

        - id: gateway_router_2
          uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/get/**
          filters:
            - MustQuery=chyb #配置必传参数名,否则访问不了

测试

案例一:


案例二:

源码分析:自定义过滤器 ------ apply()

我们以这段代码为例:

java 复制代码
@Override
public GatewayFilter apply(Object config) {
    System.out.println("进入了自定义网关过滤器MyGatewayFilterFactory,status:" + config);
    return new GatewayFilter() {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            exchange.getAttributes().put(BEGIN_TIME, System.currentTimeMillis());
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
//                .....
            }));
        }
    };
}

这里我们把重心放在:public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {}

这里的第一个参数exchange:里面封装了三个参数(上面自定义全局过滤器注释有,所以不多讲 getRequest、getResponse、getAttributes)

第二个参数chain:主要是用来进行放行操作的,而这里是怎么放行的我们就从过滤链列表构建过滤器链的执行流程

过滤链列表的构建:

过滤链列表由来:全局过滤器列表路由过滤器列表 动态合并而成

  1. 全局过滤器列表

    是根据Spring容器扫描所有的 GlobalFilter 的实现类,

    通过 @Order 注解或 Ordered 接口的 getOrder()

    方法对全局过滤器进行排序,形成全局过滤器列表。从而形成的一个列表。

  2. 路由过滤器列表

    从配置文件(如 YAML)或代码中解析路由规则,提取每个路由的 filters 列表。

    这里还有一个点要注意:配置文件中的过滤器名称(如 AddRequestHeader),会默认转化为 GatewayFilter实例

    (也就是我们自定义的路由过滤器类),

    然后根据所有的 GatewayFilter实例添加到 路由过滤器列表

  3. 动态合并而成 :

    当请求到达时,网关根据当前请求匹配的路由,将 全局过滤器列表路由过滤器列表 合并。

    路由过滤器按配置顺序追加到全局过滤器之后,便形成了过滤链。排序规则[全局过滤器按 @Order 排序(值越小优先级越高)]

    源码示例(合并):FilteringWebHandler类中的handle()

java 复制代码
public Mono<Void> handle(ServerWebExchange exchange) {
    // 1. 获取全局过滤器和路由过滤器
    List<GatewayFilter> globalFilters = this.globalFilters;
    List<GatewayFilter> routeFilters = route.getFilters();
    
    // 2. 合并过滤器列表
    List<GatewayFilter> combined = new ArrayList<>(globalFilters);
    combined.addAll(routeFilters);
    
    // 3. 排序(全局过滤器按 Order,路由过滤器按配置顺序)
    AnnotationAwareOrderComparator.sort(combined);
    
    // 4. 创建过滤器链
    return new DefaultGatewayFilterChain(combined).filter(exchange);
}

这就是形成过滤器列表的大致过程。

过滤器链的执行流程
  1. 链式调用模型:

    1. 责任链模式 :通过 DefaultGatewayFilterChain (
      这个类是FilteringWebHandler类的内部类,要想找的话可以直接搜索这个类) 递归调用每个过滤器,直到所有过滤器执行完毕。
    2. 响应式编程:使用 Mono 和 .then() 实现异步非阻塞执行。(这里不懂的话可以看一下SpringBoot的响应式编程)
  2. 核心代码展示:
    这里可以找到 chain.filter(exchange)【我们自定义过滤器中所返回的那个方法】的实现类 FilteringWebHandler
    找到内部类 DefaultGatewayFilterChain

    核心代码如下:

java 复制代码
public class DefaultGatewayFilterChain implements GatewayFilterChain {
    private final List<GatewayFilter> filters;
    private final int index; // 当前执行过滤器的索引
    @Override
    public Mono<Void> filter(ServerWebExchange exchange) {
        return Mono.defer(() -> {
            if (index < filters.size()) {
                // 1. 获取当前过滤器
                GatewayFilter filter = filters.get(index);
                // 2. 创建下一个链(index+1)
                GatewayFilterChain nextChain = new DefaultGatewayFilterChain(this, index + 1);
                // 3. 执行当前过滤器,传递下一个链
                return filter.filter(exchange, nextChain);
            } else {
                // 4. 所有过滤器执行完毕
                return Mono.empty();
            }
        });
    }
}
  1. 执行顺序示例
    假设合并后的过滤器列表为:[GlobalFilterA, GlobalFilterB, RouteFilter1, RouteFilter2]

执行流程:

  1. GlobalFilterA 的前置逻辑 → 调用 chain.filter()
  2. GlobalFilterB 的前置逻辑 → 调用 chain.filter()
  3. RouteFilter1 的逻辑 → 调用 chain.filter()
  4. RouteFilter2 的逻辑 → 调用 chain.filter()
  5. 路由到目标服务 → 返回响应
  6. RouteFilter2 的后置逻辑(通过 .then())
  7. RouteFilter1 的后置逻辑
  8. GlobalFilterB 的后置逻辑
  9. GlobalFilterA 的后置逻辑。
    这里可以自己打几个断点到DefaultGatewayFilterChain类中一步一步的查看源码查看,会更清晰一些

上述大部分代码以上传到gitee:https://gitee.com/banhuayue/springCloud-Alibaba-code.git

笔记参考来自尚硅谷

相关推荐
孤狼程序员4 小时前
【Spring Cloud微服务】6.通信的利刃:深入浅出 Spring Cloud Feign 实战与原理
spring·spring cloud·微服务
爱笑的源码基地5 小时前
智慧工地源码
java·人工智能·物联网·spring cloud·源码·智慧工地·数字工地
切糕师学AI1 天前
.Net Core Web 架构(管道机制)的底层实现
中间件·系统架构·kestrel·mvc·.netcore·路由·请求管道
切糕师学AI1 天前
浏览器访问 ASP.NET Core wwwroot 目录下静态资源的底层实现
后端·中间件·kestrel·管道·.net core web
MrSYJ1 天前
会了就涨工资的技巧:oauth2.0资源服务器配置
spring cloud·微服务·架构
F-ine1 天前
若依cloud集训总结
java·spring cloud
zt1985q2 天前
外网访问个人 IT 工具箱 it-tools
服务器·spring cloud·网络安全·云原生·eureka
孤狼程序员2 天前
【Spring Cloud 微服务】5.架构的智慧枢纽:深度剖析 Nacos 注册中心
spring cloud·微服务·架构
xiao-xiang2 天前
zookeeper-保姆级配置说明
分布式·zookeeper·云原生·中间件·zk
亦安✘2 天前
服务器从0到1微服务所需的环境的安装
运维·服务器·spring cloud·微服务