3 Star 14 Fork 3

lcry / SpringCloud2020

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README

SpringCloud2020学习笔记

技术选型

依赖 版本
SpringCloud Hoxton.SR1
SpringBoot 2.2.2.RELEASE
SpringCloud Alibaba 2.1.0.RELEASE

工具汇总

名称 版本 备注 下载地址
Java 8 官网下载
Maven 3.5+ 官网下载
MySQL 5.7+ 官网下载
IntelliJ IDEA 2019.3.3 官网下载
zookeeper 3.4.14 官网下载
nacos-server 1.2.0 nacos服务端 Github下载
consul-server 1.7.2 consul服务端 官网下载
zipkin-server 2.12.9 zipkin链路监控服务端 第三方下载
postman 7.20.1 接口请求测试 官网下载
Seata 0.9.0 分布式事务服务端 官网下载

项目中使用的数据库文件在项目根目录resouces/SQL/下,阶段代码直接查看提交记录

配套视频:https://www.bilibili.com/video/av93813318

项目结构说明

项目名 用途 所用技术
cloud-api-commons 公共代码部分(返回结果集、实体等) \
cloud-consumer-consul-order80 消费方通过Consul调用服务方 注册中心(Consul)、远程调用(RestTemplate)
cloud-consumer-feign-order80 消费方通过feign远程调用服务方 服务调用(Openfeign)、注册中心(EurekaClient)
cloud-consumer-order80 消费方通过Eureka调用服务方等示例 注册中心(Eureka)、远程调用(RestTemplate)、链路追踪(Sleuth)
cloud-consumer-zk-order80 消费方通过Zk调用服务方 注册中心(Zk)、远程调用(RestTemplate)
cloud-eureka-server7001 Eureka服务端1 注册中心(EurekaServer)
cloud-eureka-server7002 Eureka服务端2 注册中心(EurekaServer)
cloud-provider-consul-payment8006 服务方通过Consul注册服务 注册中心(Consul)
cloud-provider-payment8001 服务方通过Eureka注册服务 注册中心(Euraka)、链路追踪(Sleuth)
cloud-provider-payment8002 服务方通过Eureka注册服务 注册中心(Euraka)
cloud-provider-zk-payment8004 服务方通过Zk注册服务 注册中心(Zk)
cloud-provider-hystrix-payment8001 服务方通过Hystrix服务降级和熔断 服务降级和熔断(Hystrix)、服务监控(Actuator)
cloud-consumer-feign-hystrix-order80 消费方消费方通过feign远程调用服务方带降级和服务 服务降级和熔断(Hystrix)、服务调用(Openfeign)
cloud-consumer-hystrix-dashboard9001 Hystrix监控仪表盘 服务监控(Hystrix)
cloud-gateway-gateway9527 网关服务GateWay 网关(GateWay)
cloud-config-center-3344 配置中心服务端 配置中心(Config-Server)、服务总线(BUS)
cloud-config-client-3355 配置中心客户端1 配置中心(Config)、服务总线(BUS)
cloud-config-client-3356 配置中心客户端1 配置中心(Config)、服务总线(BUS)
cloud-stream-provider-rabbitmq8801 消息驱动发送方 消息驱动(Stream)
cloud-stream-provider-rabbitmq8802 消息驱动接受方1 消息驱动(Stream)
cloud-stream-consumer-rabbitmq8803 消息驱动接受方2 消息驱动(Stream)
cloudalibaba-config-nacos-client3377 配置中心客户端Nacos 配置中心(Nacos)、服务注册(Nacos)
cloudalibaba-provider-nacos-payment9001 服务方通过Nacos注册服务1 服务注册(Nacos)
cloudalibaba-provider-nacos-payment9002 服务方通过Nacos注册服务2 服务注册(Nacos)
cloudalibaba-provider-nacos-payment9003 服务方通过Nacos注册服务模拟查询1 服务注册(Nacos)
cloudalibaba-provider-nacos-payment9004 服务方通过Nacos注册服务模拟查询2 服务注册(Nacos)
cloudalibaba-sentinel-service8401 服务限流和熔断Sentinel示例 服务注册(Nacos)、服务限流和熔断(Sentinel)
cloudalibaba-consumer-nacos-order84 消费方通过整合Feign和Sentinel 服务注册(Nacos)、服务限流和熔断(Sentinel)、服务调用(Openfeign)、负载均衡(Rabbin)
cloudalibaba-seata-order-service12001 分布式事务商品下单微服务 分布式事务(Seata)、服务注册(Nacos)、服务调用(Openfeign)、负载均衡(Rabbin)
cloudalibaba-seata-storage-service12002 分布式事务库存微服务 分布式事务(Seata)、服务注册(Nacos)、服务调用(Openfeign)、负载均衡(Rabbin)
cloudalibaba-seata-account-service12003 分布式事务账户微服务 分布式事务(Seata)、服务注册(Nacos)、服务调用(Openfeign)、负载均衡(Rabbin)

知识点整理

一、服务注册与发现

1、使用Eureka服务端

1)导入依赖

        <!--eureka-server-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <!--热部署推荐一起-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

2)application.yml示例

server:
  port: 7001

eureka:
  instance:
    hostname: eureka7001.com #Eureka服务端的实例名称
  client:
    #false表示不向注册中心中注册自己
    register-with-eureka: false
    #false表示自己就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    fetch-registry: false
    service-url:
      #设置与Eureka Server之间交互的地址,查询服务和注册服务都需要依赖这个地址
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #单机版,指向自己
      #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版
  server:
    #关闭Eureka自我保护机制,保证不可用服务被及时剔除
    enable-self-preservation: false
    eviction-interval-timer-in-ms: 2000

3)启动类添加注解@EnableEurekaServer

@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {
    public static void main(String[] args) {
        SpringApplication.run(EurekaMain7001.class, args);
    }
}

4)验证

访问:http://localhost:7001

对应项目:cloud-eureka-server7001、cloud-eureka-server7002

2、使用Eureka客户端

1)导入依赖

        <!--引入eureka客户端eureka-client -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

2)application.yml示例

server:
  port: 8001

spring:
  application:
    name: cloud-payment-service
    
eureka:
  client:
    #表示是否将自己注册进EurekaServer,默认为true
    register-with-eureka: true
    #是否从EurekaServer中抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetchRegistry: true
    service-url:
      defaultZone: http://localhost:7001/eureka #单机版
      #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版
  instance:
    instance-id: payment8001 #修改Status名称
    prefer-ip-address: true
    #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认30)
    lease-renewal-interval-in-seconds: 10
    #Eureka服务端在收到最后一次心跳后等待的时间上限,单位为秒(默认90),超时剔除服务
    lease-expiration-duration-in-seconds: 60

3)启动类添加注解@EnableDiscoveryClient(推荐)或者@EnableEurekaClient

@SpringBootApplication
@EnableDiscoveryClient //Discovery服务发现
public class PaymentMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8001.class, args);
    }
}

对应项目:cloud-provider-payment8001、cloud-provider-payment8002等

3、使用Zookeeper注册服务

1)导入依赖

        <!-- SpringBoot整合zookeeper客户端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
            <!-- 先排除自带的zookeeper -->
            <exclusions>
                <exclusion>
                    <groupId>org.apache.zookeeper</groupId>
                    <artifactId>zookeeper</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- 添加本机zookeeper3.4.14版本 -->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.14</version>
        </dependency>

2)application.yml示例

server:
  port: 8004

#服务别名——注册到zookeeper注册中心的名称
spring:
  application:
    name: cloud-provider-payment
  cloud:
    zookeeper:
      connect-string: 127.0.0.1:2181

3)启动类添加注解@EnableDiscoveryClient

@SpringBootApplication
@EnableDiscoveryClient  //该注解用于向使用zookeeper作为注册中心时注册服务
public class ZkPaymentMain8004 {
    public static void main(String[] args) {
        SpringApplication.run(ZkPaymentMain8004.class, args);
    }
}

对应项目:cloud-provider-zk-payment8004

4、使用Consul注册服务

1)导入依赖

    <!--    consul软件官方下载:https://www.consul.io/downloads.html
    开发模式启动:consul agent -dev
    验证:浏览器访问 http://localhost:8500
    若提示:
Consul returned an error. You may have visited a URL that is loading an unknown resource,so you can try going back to the root or try re-submitting your ACL Token/SecretID by going back to ACLs.
    错误请使用其他浏览器访问试试
    -->
    <!--consul版服务提供方-->

		<!--SpringCloud consul-server -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>

2)application.yml示例

server:
  port: 8006

spring:
  application:
    name: consul-provider-payment
  #consul注册中心地址
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        #hostname: 127.0.0.1
        service-name: ${spring.application.name}

3)启动类添加注解@EnableDiscoveryClient

@SpringBootApplication
@EnableDiscoveryClient
public class ConsulPaymentMain8006 {
    public static void main(String[] args) {
        SpringApplication.run(ConsulPaymentMain8006.class, args);
    }
}

对应项目:cloud-provider-consul-payment8006

二、负载均衡调用

1、使用Ribbon负载均衡

默认eureka集成了rabbin,配置即可使用,默认是轮训策略。

1)新建规则类MySelfRule.java

/**
 * MySelfRule
 *
 * @author lcry
 * 自定义Ribbon默认轮训规则类
 */
@Configuration
public class MySelfRule {
    @Bean
    public IRule myRule() {
        return new RandomRule();//定义为随机
    }
}

IRule实现类有如下:

com.netflix.loadbalancer.RoundRobinRule:轮询

com.netflix.loadbalancer.RandomRule:随机

com.netflix.loadbalancer.RetryRule:先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内进行重试,获取可用的服务

WeightedResponseTimeRule:对RoundRobinRule的扩展,响应速度越快的实例选择权重越多大,越容易被选择

BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务

AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例

ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能和server的可用性选择服务器

2)启动类添加注解@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)//启用Ribbon

@SpringBootApplication
@EnableDiscoveryClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)//启用Ribbon
public class OrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain80.class, args);
    }
}

若使用restTemplate直接使用轮训规则,可以直接在配置类上@LoadBalanced注解

/**
 * ApplicationContextConfig
 *
 * @author lcry
 * @date 2020/03/14 12:05
 * 配置RestTemplate
 */
@Configuration
public class ApplicationContextConfig {
    @Bean //相当于Spring中applicationContext.xml中<bean id="" class="">
    @LoadBalanced //使用此注解赋予RestTemplate负载均衡的能力,测试自定义算法注释掉
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

对应项目:cloud-consumer-order80

2、使用OpenFeign

1)导入依赖

        <!-- openfeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

2)application.yml示例

server:
  port: 80

eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://localhost:7001/eureka #单机版
      #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版

#OpenFeign的超时控制配置:默认只等待1秒,超时则报错
#设置Feign客户端超时时间(OpenFeign默认支持Ribbon)
ribbon:
  #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  ReadTimeout: 5000
  #指的是建立连接后从服务器读取到可用资源所用的时间
  connectTimeout: 5000

#开启Feign的日志客户端
logging:
  level:
    #Feign日志以什么级别监听哪个接口
    com.lcry.springcloud.service.PaymentFeignService: debug

3)启动类添加注解@EnableFeignClients

@SpringBootApplication
//启用Feign
@EnableFeignClients
public class FeignOrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(FeignOrderMain80.class, args);
    }
}

4)配置日志打印信息 FeignConfig.java

/**
 * FeignConfig
 *
 * @author lcry
 * @date 2020/03/14 22:04
 * Feign日志控制
 */
@Configuration
public class FeignConfig {
    @Bean
    Logger.Level feignLoggerLevel() {
        /**
         * 日志级别:
         * NONE :不记录任何日志(默认)
         * BASIC:仅记录请求方法、URL、响应状态代码以及执行时间
         * HEADERS:记录BASIC级别的基础上,记录请求和响应的header
         * FULL:记录请求和响应的header,body和元数据
         */
        return Logger.Level.FULL;
    }
}

5)远程调用服务

/**
 * PaymentFeignService
 *
 * @author lcry
 */
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE") //需要调用的目标微服务应用名称
public interface PaymentFeignService {
    //Feign接口中的方法要与目标服务中的Controller中的方法完全一致,可以直接copy
    @GetMapping(value = "/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);

    @GetMapping(value = "/payment/feign/timeout")
    public String paymentFeignTimeout();
}

对应项目:cloud-consumer-feign-order80

三、服务熔断与降级

1、使用HyStrix熔断和降级

1)导入依赖

        <!--hystrix-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

2)application.yml示例

server:
  port: 8001

spring:
  application:
    name: cloud-provider-hystrix-payment

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://127.0.0.1:7001/eureka
      #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

3)启动类添加注解@EnableHystrix

@SpringBootApplication
@EnableEurekaClient
@EnableHystrix  //服务端开启Hystrix或者使用@EnableCircuitBreaker
public class HystrixPaymentMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixPaymentMain8001.class, args);
    }
}

4)服务降级示例代码,@HystrixCommand注解实现

/**
     * 模拟超时3s
     *
     * @param id
     * @return
     */
    @Override
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler", commandProperties = {
            //超时5s或者异常直接降级
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000")
    })
    public String paymentInfo_TimeOut(Integer id) {
        //int age = 10/0;
        int timenum = 3;
        try {
            TimeUnit.SECONDS.sleep(timenum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "线程池:  " + Thread.currentThread().getName() + " id:  " + id + "\t" + "O(∩_∩)O哈哈~" + "  耗时(秒): " + timenum;
    }

    public String paymentInfo_TimeOutHandler(Integer id) {
        return "线程池:  " + Thread.currentThread().getName() + "  8001系统繁忙或者运行报错,请稍后再试,id:  " + id + "\t" + "o(╥﹏╥)o";
    }

5)服务熔断示例代码

    /**
     * 以下内容属于服务熔断
     *
     * @param id
     * @return
     */
    @Override
    @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", commandProperties = {
            @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),// 是否开启断路器
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),// 请求次数
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), // 时间窗口期
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"),// 失败率达到多少后跳闸
    })
    public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
        if (id < 0) {
            throw new RuntimeException("id 不能负数");
        }
        String serialNumber = IdUtil.simpleUUID();

        return Thread.currentThread().getName() + "\t" + "调用成功,流水号: " + serialNumber;
    }

    public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
        return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~   id: " + id;
    }

对应项目:cloud-provider-hystrix-payment8001、cloud-consumer-feign-order80

2、使用HystrixDashboard监控仪表盘

1)导入依赖

    <!--  hystrix服务监控平台dashboard  -->
    <!-- 被监控服务一定要引入web和actuator两个依赖   -->
    <!--
    监控地址:http://ip:端口/hystrix
    被监控填写:http://ip:端口/hystrix.stream

    -->
 <!--     hystrix-dashboard   -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

2)application.yml示例

server:
  port: 9001

3)启动类添加注解@EnableHystrixDashboard

/**
 * HystrixDashboardMain9001
 *
 * @author lcry
 */
@SpringBootApplication
@EnableHystrixDashboard  //启用注解
public class HystrixDashboardMain9001 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixDashboardMain9001.class, args);
    }
}

4)被监控端配置

  • 引入依赖

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
  • 启动类注入

        /**
         * 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
         * ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
         * 只要在自己的项目里配置上下面的servlet就可以了
         */
        @Bean
        public ServletRegistrationBean getServlet() {
            HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
            ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
            registrationBean.setLoadOnStartup(1);
            registrationBean.addUrlMappings("/hystrix.stream");
            registrationBean.setName("HystrixMetricsStreamServlet");
            return registrationBean;
        }

5)验证

访问hystrix服务监控平台http://localhost:9001,被监控填写:http:// ip:端口/hystrix.stream

对应项目:cloud-consumer-hystrix-dashboard9001、cloud-provider-hystrix-payment8001

四、路由网关

1、使用Zuul

忽略,直接使用Gateway新一代网关

2、使用SpringCloud Gateway

1)导入依赖

        <!--gateway-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--eureka-client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--引入自己定义的api调用包,可以使用Payment支付Entity-->
        <dependency>
            <groupId>com.lcry</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

2)通过application.yml配置路由网关示例(推荐)

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能利用微服务名进行路由
      routes:
        - id: payment_route #payment_route    #路由的ID没有固定规则但要求唯一建议配合服务名
          #uri: http://localhost:8001          #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/**         # 断言路径相匹配的进行路由

        - id: payment_route2 #payment_route    #路由的ID没有固定规则但要求唯一建议配合服务名
          #uri: http://localhost:8001          #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/lb/**         # 断言路径相匹配的进行路由
            #- After=2020-02-21T15:51:37.485+08:00[Asia/Shanghai]
            #- Cookie=username,zzyy
            #- Header=X-Request-Id, \d+  # 请求头要有X-Request-Id属性并且值为整数的正则表达式

eureka:
  instance:
    hostname: cloud-gateway-service
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://localhost:7001/eureka

3)通过编码方式实现路由网关

/**
 * 通过编码方式实现路由
 */
@Configuration
public class GateWayConfig {
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();

        routes.route("path_route_baidunews",
                r -> r.path("/guonei")
                        .uri("http://news.baidu.com/guonei")).build();

        return routes.build();
    }
}

4)自定义过滤逻辑示例 MyLogGateWayFilter.java

/**
 * 自定义过滤器示例-一般用于鉴权或者全局日志
 */
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("***********come in MyLogGateWayFilter:  " + new Date());

        //请求参数必须带username,并且不能为空
        String uname = exchange.getRequest().getQueryParams().getFirst("username");

        if (StrUtil.isEmpty(uname)) {
            log.info("*******用户名为空,非法用户,o(╥﹏╥)o");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }

        return chain.filter(exchange);  //放行给下一条过滤链
    }

    @Override
    public int getOrder() {
        //执行顺序,越小越先执行
        return 0;
    }
}

对应项目:cloud-gateway-gateway9527

五、分布式配置中心

1、使用SpringCloud Config服务端

1)导入依赖

        <!-- config-server依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>

2)application.yml示例

server:
  port: 3344

spring:
  application:
    name:  cloud-config-center #注册进Eureka服务器的微服务名
  cloud:
    config:
      server:
        git:
          uri: git@gitee.com:lcry/springcloud-config.git #git仓库地址
          ####搜索目录
          search-paths:
            - springcloud-config
      ####读取分支
      label: master
#服务注册到eureka地址
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka

3)启动类添加注解@EnableConfigServer

@SpringBootApplication
@EnableConfigServer  //启用注册中心服务端
public class ConfigCenterMain3344 {
    public static void main(String[] args) {
        SpringApplication.run(ConfigCenterMain3344.class, args);
    }
}

对应项目:cloud-config-center-3344

2、使用SpringCloud Config客户端

1)导入依赖

        <!--        spring-config客户端依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>

2)application.yml示例

server:
  port: 3355

spring:
  application:
    name: config-client
  cloud:
    #Config客户端配置
    config:
      label: master #分支名称
      name: config #配置文件名称
      profile: dev #读取后缀名称   上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
      uri: http://localhost:3344 #配置中心地址server:
  port: 3355

spring:
  application:
    name: config-client
  cloud:
    #Config客户端配置
    config:
      label: master #分支名称
      name: config #配置文件名称
      profile: dev #读取后缀名称   上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
      uri: http://localhost:3344 #配置中心地址

对应项目:cloud-config-client-3355、cloud-config-client-3356

六、消息总线

1、使用SpringCloud Bus刷新配置

1)导入依赖

        <!--添加消息总线RabbitMQ支持-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>

2)SpringCloud Config服务端application.yml示例

#rabbitmq相关配置
  rabbitmq:
    host: 192.168.179.150
    port: 5672
    username: admin
    password: admin

#全局刷新POST请求 http://注册中心地址/actuator/bus-refresh
#定点通知:(只通知部分)http://注册中心地址/actuator/refresh/{服务名称:端口}
#如:http://localhost:3344/actuator/bus-refresh/config-client:3355
##rabbitmq相关配置,暴露bus刷新配置的端点
management:
  endpoints: #暴露bus刷新配置的端点
    web:
      exposure:
        include: 'bus-refresh'

3)SpringCloud Config客户端application.yml示例

#rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
  rabbitmq:
    host: 192.168.179.150
    port: 5672
    username: admin
    password: admin

#  手动刷新地址POST http://客户端访问地址/actuator/refresh
# curl -X POST "http://localhost:3355/actuator/refresh"
# 暴露监控端点
management:
  endpoints:
    web:
      exposure:
        include: "*"

4)刷新配置注解@RefreshScope

@RestController
@RefreshScope //刷新配置生效
public class ConfigClientController {

    @Value("${server.port}")
    private String serverport;

    @Value("${config.info}")
    private String configInfo;

    //    直接获取配置文件
    @GetMapping("/configInfo")
    public String getConfigInfo() {
        return configInfo + "服务端口:"+serverport;
    }

}

对应项目:cloud-config-center-3344、cloud-config-center-3355、cloud-config-center-3356

七、消息驱动

1、使用Stream发送方

1)导入依赖

        <!--        引入stream-rabbit-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
        </dependency>

2)application.yml示例

server:
  port: 8801

spring:
  application:
    name: cloud-stream-provider
  cloud:
      stream:
        binders: # 在此处配置要绑定的rabbitmq的服务信息;
          defaultRabbit: # 表示定义的名称,用于于binding整合
            type: rabbit # 消息组件类型
            environment: # 设置rabbitmq的相关的环境配置
              spring:
                rabbitmq:
                  host: 192.168.179.150
                  port: 5672
                  username: admin
                  password: admin
        bindings: # 服务的整合处理
          output: # 这个名字是一个通道的名称
            destination: studyExchange # 表示要使用的Exchange名称定义
            content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
            binder: defaultRabbit # 设置要绑定的消息服务的具体设置

eureka:
  client: # 客户端进行Eureka注册的配置
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
    lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
    instance-id: cloud-stream-provider8801  # 在信息列表时显示主机名称
    prefer-ip-address: true     # 访问的路径变为IP地址

3)定义推送管道

/**
 * //定义消息的推送管道
 */
@EnableBinding(Source.class) //定义消息的推送管道,生产者固定写法
@Slf4j
public class MessageProviderImpl implements IMessageProvider {
    @Resource
    private MessageChannel output; // 消息发送管道

    @Override
    public String send() {
        String serial = UUID.randomUUID().toString();
        output.send(MessageBuilder.withPayload(serial).build());
        log.info(String.format("生成的流水号:{%s}", serial));
        return serial;
    }
}

对应项目:cloud-stream-provider-rabbitmq8801

2、使用Stream接受方

1)导入依赖

        <!--      stream-rabbit  -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
        </dependency>

2)application.yml示例

server:
  port: 8802

spring:
  application:
    name: cloud-stream-consumer
  cloud:
      stream:
        binders: # 在此处配置要绑定的rabbitmq的服务信息;
          defaultRabbit: # 表示定义的名称,用于于binding整合
            type: rabbit # 消息组件类型
            environment: # 设置rabbitmq的相关的环境配置
              spring:
                rabbitmq:
                  host: 192.168.179.150
                  port: 5672
                  username: admin
                  password: admin
        bindings: # 服务的整合处理
          input: # 这个名字是一个通道的名称
            destination: studyExchange # 表示要使用的Exchange名称定义
            content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
            binder: defaultRabbit # 设置要绑定的消息服务的具体设置
            group: receiveclientA  #分组避免重复消费和持久化问题

eureka:
  client: # 客户端进行Eureka注册的配置
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
    lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
    instance-id: cloud-stream-consumer8802  # 在信息列表时显示主机名称
    prefer-ip-address: true     # 访问的路径变为IP地址

3)接收消息逻辑代码

@Component
@EnableBinding(Sink.class)
public class ReceiveMessageListenerController {
    @Value("${server.port}")
    private String serverPort;


    @StreamListener(Sink.INPUT)  //固定的写法,对应yml配置的通道名称
    public void input(Message<String> message) {
        System.out.println("消费者1号,----->接受到的消息: " + message.getPayload() + "\t  port: " + serverPort);
    }
}

对应项目:cloud-stream-consumer-rabbitmq8802、cloud-stream-consumer-rabbitmq8802

八、分布式链路追踪

1、使用sleuth客户端

1)导入依赖

        <!--链路监控包含了sleuth+zipkin-->
        <!--服务端下载地址:https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>
        <!--引入eureka客户端eureka-client -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

2)application.yml示例

spring:
  application:
    name: cloud-payment-service
  #链路监控配置,配置链路监控服务端地址
  zipkin:
    base-url: http://localhost:9411
  sleuth:
    sampler:
    #采样率值介于 0 到 1 之间,1 则表示全部采集
    probability: 1

对应项目:cloud-provider-payment8001、cloud-consumer-order80

九、SpringCloud alibaba

1、使用Nacos服务注册

1)导入依赖

        <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

2)application.yml示例

server:
  port: 9001

spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #配置Nacos地址,注册服务到Nacos

management:
  endpoints:
    web:
      exposure:
        include: '*'

3)启动类添加注解@EnableDiscoveryClient

/**
 * NacosPaymentMain9001
 *
 * @author lcry
 * @date 2020/03/16 18:03
 */
@EnableDiscoveryClient
@SpringBootApplication
public class NacosPaymentMain9001 {
    public static void main(String[] args) {
        SpringApplication.run(NacosPaymentMain9001.class,args);
    }

}

对应项目:cloudalibaba-provider-nacos-payment9001、cloudalibaba-provider-nacos-payment9002

2、使用Nacos注册中心

1)导入依赖

<!--    nacos服务直接到官网下载以jar运行即可-->
		<!--nacos-config-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!--nacos-discovery-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--web + actuator-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

2)application.yml示例

#激活配置文件选项
spring:
  profiles:
    active: dev # 表示开发环境
    #active: test # 表示测试环境
    #active: prod # 表示生产环境

# nacos配置
server:
  port: 3377

spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
      config:
        server-addr: localhost:8848 #Nacos作为配置中心地址
        namespace: Test-NameSpace-HA1   #命名空间ID,nacos1.2可以自定义
        group: DEV_GROUP   #分组ID
        file-extension: yaml #指定yaml格式的配置
#
#命名规范:
# ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
# nacos-config-client-dev.yaml

3)启动类添加注解@EnableDiscoveryClient

/**
 * ConfigNacosMain3344
 *
 * @author lcry
 * @date 2020/03/16 17:49
 */
@SpringBootApplication
@EnableDiscoveryClient
public class ConfigNacosMain3377 {
    public static void main(String[] args) {
        SpringApplication.run(ConfigNacosMain3377.class, args);
    }
}

4)业务类添加注解@RefreshScope

/**
 * NacosClientController
 *
 * @author lcry
 */
@RestController
@RefreshScope //支持Nacos的动态刷新功能。
public class NacosClientController {
    @Value("${config.info}")  //通过nacos配置中心获取文件
    private String configInfo;

    @GetMapping("/config/info")
    public String getConfigInfo() {
        return configInfo;
    }
}

对应项目:cloudalibaba-config-nacos-client3377

3、使用Sentiel限流

1)导入依赖

        <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>
        <!--SpringCloud ailibaba sentinel -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

2)application.yml配置示例

#测试端口
server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
    sentinel:
      transport:
        dashboard: localhost:8080 #配置Sentinel dashboard地址
        port: 8719  #随机找一个地址,如果冲突就往后+1
        #持久化配置
#      datasource:
#        ds1:
#          nacos:
#            server-addr: localhost:8848
#            dataId: cloudalibaba-sentinel-service
#            groupId: DEFAULT_GROUP
#            data-type: json
#            rule-type: flow

management:
  endpoints:
    web:
      exposure:
        include: '*'

feign:
  sentinel:
    enabled: true # 激活Sentinel对Feign的支持

3)接口先请求一次,然后在sentinel控制台进行相应限流模式操作

4)自定义限流异常页面等,使用注解@SentinelResource

    @GetMapping("/testHotKey")
    @SentinelResource(value = "testHotKey", blockHandler = "deal_testHotKey")
    public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
                             @RequestParam(value = "p2", required = false) String p2) {
        //int age = 10/0;
        return "------testHotKey";
    }

    public String deal_testHotKey(String p1, String p2, BlockException exception) {
        //sentinel系统默认的提示:Blocked by Sentinel (flow limiting)
        return "------deal_testHotKey,o(╥﹏╥)o";

    }


	    @GetMapping("/rateLimit/byResource")
    @SentinelResource(value = "byResource", blockHandler = "handleException")
    public CommonResult byResource() {
        return new CommonResult(200, "按资源名称限流测试OK", new Payment(2020L, "serial001"));
    }

    public CommonResult handleException(BlockException exception) {
        return new CommonResult(444, exception.getClass().getCanonicalName() + "\t 服务不可用");
    }

    @GetMapping("/rateLimit/byUrl")
    @SentinelResource(value = "byUrl")
    public CommonResult byUrl() {
        return new CommonResult(200, "按url限流测试OK", new Payment(2020L, "serial002"));
    }


    @GetMapping("/rateLimit/customerBlockHandler")
    @SentinelResource(value = "customerBlockHandler",     //资源名称
            blockHandlerClass = CustomerBlockHandler.class,  //异常自定义
            blockHandler = "handlerException1")   //调用自定义类某个方法
    public CommonResult customerBlockHandler() {
        return new CommonResult(200, "按客戶自定义", new Payment(2020L, "serial003"));
    }
/**
 * @author lcry
 * @date 2020/03/17 14:51
 * 用户自定义处理异常类
 */
public class CustomerBlockHandler {
    public static CommonResult handlerException1(BlockException exception) {
        return new CommonResult(4444, "按客戶自定义,global handlerException----1");
    }

    public static CommonResult handlerException2(BlockException exception) {
        return new CommonResult(4444, "按客戶自定义,global handlerException----2");
    }
}

对应项目:cloudalibaba-sentinel-service8401

4、使用Sentinel服务熔断

1)导入依赖

 <!--    服务消费方,整合rabbin和feign,sentinel限流熔断等-->
		<!--SpringCloud openfeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--SpringCloud ailibaba sentinel-datasource-nacos做持久化用到-->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>
        <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--SpringCloud ailibaba sentinel -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

2)启动类添加启用Openfeign注解@EnableFeignClients

@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients  //整合feign
public class NacosOrderMain84 {
    public static void main(String[] args) {
        SpringApplication.run(NacosOrderMain84.class, args);
    }
}

3)熔断和降级示例业务代码

/**
 * CircleBreakerController
 *
 * @author lcry
 * 使用示例:
 */
@RestController
@Slf4j
public class CircleBreakerController {
    public static final String SERVICE_URL = "http://nacos-payment-provider";

    //    整个rabbin - restTemplate
    @Resource
    private RestTemplate restTemplate;

    //    整合sentinel
    @RequestMapping("/consumer/fallback/{id}")
    //@SentinelResource(value = "fallback") //没有配置
    //@SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback只负责业务异常
    //@SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只负责sentinel控制台配置违规
//    @SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler",  //分开自定义
//            exceptionsToIgnore = {IllegalArgumentException.class}//排除异常不限流
//            )
    @SentinelResource(value = "fallback", //,fallback = "handlerFallback",
            blockHandlerClass = CustomerBlockHandler.class,  //异常自定义
            blockHandler = "handlerException1"  //分开自定义
    )
    public CommonResult<Payment> fallback(@PathVariable Long id) {
        CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);

        if (id == 4) {
            throw new IllegalArgumentException("IllegalArgumentException,非法参数异常....");
        } else if (result.getData() == null) {
            throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
        }

        return result;
    }

    //本例是fallback
    public CommonResult handlerFallback(@PathVariable Long id, Throwable e) {
        Payment payment = new Payment(id, "null");
        return new CommonResult<>(444, "兜底异常handlerFallback,exception内容  " + e.getMessage(), payment);
    }

    //本例是blockHandler
    public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) {
        Payment payment = new Payment(id, "null");
        return new CommonResult<>(445, "blockHandler-sentinel限流,无此流水: blockException  " + blockException.getMessage(), payment);
    }

    //OpenFeign调用
    @Resource
    private PaymentService paymentService;

    @GetMapping(value = "/consumer/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
        return paymentService.paymentSQL(id);
    }


    /*
   测试自定义限流
     */
    @GetMapping(value = "/consumer/paymentSQL")
    @SentinelResource(value = "customerBlockHandler",     //资源名称
            blockHandlerClass = CustomerBlockHandler.class,  //异常自定义
            blockHandler = "handlerException1")   //调用自定义类某个方法
    public CommonResult<Payment> paymentSQL2() {
        return paymentService.paymentSQL(1L);
    }
}

对应项目:cloudalibaba-consumer-nacos-order84

5、使用分布式事务Seata

1)引入依赖

<!--nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <!--            排除自带的,我们使用0.9.0-->
            <exclusions>
                <exclusion>
                    <artifactId>seata-all</artifactId>
                    <groupId>io.seata</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>0.9.0</version>
        </dependency>
        <!--feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

2)application.yml配置示例

#微服务端口
server:
  port: 12001

spring:
  application:
    name: seata-order-service
  cloud:
    alibaba:
      seata:
        #自定义事务组名称需要与seata-server中的对应
        tx-service-group: fsp_tx_group
    nacos:
      discovery:
        server-addr: localhost:8848
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: lcry

#禁用feign
feign:
  hystrix:
    enabled: false
#OpenFeign的超时控制配置:默认只等待1秒,超时则报错
#设置Feign客户端超时时间(OpenFeign默认支持Ribbon)
ribbon:
  #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  ReadTimeout: 5000
  #指的是建立连接后从服务器读取到可用资源所用的时间
  connectTimeout: 5000
#日志级别
logging:
  level:
    io:
      seata: info
#mybatis映射
mybatis:
  mapperLocations: classpath:mapper/*.xml

3)配置file.conf和register.conf,可以直接从Seata上直接拷贝到resources目录下做部分改动

4)业务代码添加注解@GlobalTransactional开启全局事务

    /**
     * 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
     * 简单说:下订单->扣库存->减余额->改状态
     */
    @Override
    @GlobalTransactional(name = "tx-create-order", rollbackFor = Exception.class)
    public void create(Order order) {
        log.info("----->开始新建订单");
        //1 新建订单
        orderDao.create(order);

        //2 扣减库存
        log.info("----->订单微服务开始调用库存,做扣减Count");
        storageService.decrease(order.getProductId(), order.getCount());
        log.info("----->订单微服务开始调用库存,做扣减end");

        //3 扣减账户
        log.info("----->订单微服务开始调用账户,做扣减Money");
        accountService.decrease(order.getUserId(), order.getMoney());
        log.info("----->订单微服务开始调用账户,做扣减end");

        //4 修改订单状态,从零到1,1代表已经完成
        log.info("----->修改订单状态开始");
        orderDao.update(order.getUserId(), 0);
        log.info("----->修改订单状态结束");

        log.info("----->下订单结束了,O(∩_∩)O哈哈~");

    }

对应项目:cloudalibaba-seata-order-service12001、cloudalibaba-seata-storage-service12002、cloudalibaba-seata-account-service12003

完结~

空文件

简介

2020-SpringCloud学习,技术选型:SpringCloud(Hoxton.SR1)SpringBoot(2.2.2RELEASES)SpringCloud Alibaba(2.1.0.RELEASE)Java(8) 展开 收起
Java
取消

发行版 (7)

全部

贡献者

全部

近期动态

加载更多
不能加载更多了
Java
1
https://gitee.com/lcry/SpringCloud2020.git
git@gitee.com:lcry/SpringCloud2020.git
lcry
SpringCloud2020
SpringCloud2020
master

搜索帮助