初学SpringCloud笔记(第一个版本)

  1. 学习首先要认识SpringCloud是什么?

SpringCloud是基于SpringBoot构建的。是一种完整的微服务解决方案。

  1. 为什么我们要选择SpringCloud,而不选择Dubbo?
DubboSpring Cloud
服务注册中心ZookeeperSpring Cloud Netflix Eureka
服务调用方式RPCREST API
服务监控Dubbo-monitorSpring Boot Admin
断路器不完善Spring Cloud Netflix Hystrix
服务网关Spring Cloud Netflix Zuul
分布式配置Spring Cloud Config
服务跟踪Spring Cloud Sleuth
消息总线Spring Cloud Bus
数据流Spring Cloud Stream
批量任务Spring Cloud Task

①Dubbo是什么?
Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
②dubbo有什么特点?
DUBBO有良好的连通性、健壮性、伸缩性、升级性。结合dubbo可以相对于单体系统提升系统整体的扩展性
③为什么要使用dubbo?
https://blog.youkuaiyun.com/cmj6706/article/details/78928801
https://blog.youkuaiyun.com/qq_37876078/article/details/88425369
当越来越多的垂直的应用程序,应用程序之间的交互是不可避免的,一些核心的业务被提取并作为独立服务,逐渐的形成一个稳定的服务中心,这样,前端的应用程序可以更多的响应市场需求的变化很快,此时,用于业务重用和集成的分布式的服务框架(RPC)是关键。
②RPC是什么?
Rpc是一种远程过程调用。
RPC框架的意义和用法,什么是RPC:
https://blog.youkuaiyun.com/xj15010735572/article/details/79023951

③两者最大的区别:
Spring Cloud抛弃了Dubbo 的RPC通信,采用的是基于HTTP的REST方式。
那么问题又来了。啥是HTTP的REST方式。https://blog.youkuaiyun.com/jnshu_it/article/details/77930075
严格来说,这两种方式各有优劣。虽然在一定程度上来说,后者牺牲了服务调用的性能,但也避免了上面提到的原生RPC带来的问题。而且REST相比RPC更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖,这在强调快速演化的微服务环境下,显得更为合适。

④最后我们来说一下为什么要选择StringCloud
Spring Cloud来源于Spring,质量、稳定性、持续性都可以得到保证
Spirng Cloud天然支持Spring Boot,更加便于业务落地。
Spring Cloud发展非常的快,从16年开始接触的时候相关组件版本为1.x,到现在将要发布2.x系列
Spring Cloud是Java领域最适合做微服务的框架。

相比于其它框架,Spring Cloud对微服务周边环境的支持力度最大。

对于中小企业来讲,使用门槛较低。
3.Springcloud的五大神兽
①服务发现——Netflix Eureka
作用:实现服务治理(服务注册与发现)
简介:Spring Cloud Eureka是Spring Cloud Netflix项目下的服务治理模块。
由两个组件组成:Eureka服务端和Eureka客户端。
Eureka服务端用作服务注册中心。支持集群部署。
Eureka客户端是一个java客户端,用来处理服务注册与发现。
在应用启动时,Eureka客户端向服务端注册自己的服务信息,同时将服务端的服务信息缓存到本地。客户端会和服务端周期性的进行心跳交互,以更新服务租约和服务信息。

②客服端负载均衡——Netflix Ribbon

作用:Ribbon,主要提供客户侧的软件负载均衡算法。

简介:Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。

关键点就是将外界的rest调用,根据负载均衡策略转换为微服务调用。Ribbon有比较多的负载均衡策略。

③断路器——Netflix Hystrix
作用:断路器,保护系统,控制故障范围。
简介:为了保证其高可用,单个服务通常会集群部署。由于网络原因或者自身的原因,服务并不能保证100%可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。

④服务网关——Netflix Zuul

作用:api网关,路由,负载均衡等多种作用
简介:类似nginx,反向代理的功能,不过netflix自己增加了一些配合其他组件的特性。
在微服务架构中,后端服务往往不直接开放给调用端,而是通过一个API网关根据请求的url,路由到相应的服务。当添加API网关后,在第三方调用端和服务提供方之间就创建了一面墙,这面墙直接与调用方通信进行权限控制,后将请求均衡分发给后台服务端。

⑤分布式配置——Spring Cloud Config
作用:配置管理
简介:SpringCloud Config提供服务器端和客户端。服务器存储后端的默认实现使用git,因此它轻松支持标签版本的配置环境,以及可以访问用于管理内容的各种工具。
这个还是静态的,得配合Spring Cloud Bus实现动态的配置更新。

一、Eureka的配置
1.首先建一个空的工程

引入依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.9.RELEASE</version>
</parent>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Greenwich.SR3</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

2.建一个空的子工程—服务端
①引入依赖

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

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

</dependencies>

②在resources包下创建application.yml文件

spring.application.name: eureka-server


server:
  port: 8761

eureka:
  server:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

③在java包下创建一个包并建立一个启动类EurekaServeApp

@SpringBootApplication
@EnableEurekaServer
public class EurekaServeApp {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServeApp.class);
    }
}
  1. 建立一个空的工程–客户端
    ①引入依赖
<dependencies>
    <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>
</dependencies>

②在resources包下创建application.yml文件

spring.application.name: lq-eureka-client

eureka:
  server:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

③在java包下创建一个包并建立一个启动类EurekaServeApp

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

这时候我们先启动服务端,再启动客户端,(虽然先启动哪个都可以启动,但是先启动客户端会报错),然后我们进入http://localhost:8761/接可以看到
在这里插入图片描述

这个时候我们eureka就已经配置完了。

二、负载均衡的搭建

① 首先我们需要了解负载均衡是个是什么意思
在这里插入图片描述

1:user微服务1、user微服务2、user微服务3是一个服务集群,它们都会向注册中心注册服务(它们的应用名都是USER-SERVICE)
2:注册中心记录集群元数据信息,即USER-SERVICE下有3个服务节点
3:Ribbon拦截所有的请求,从请求信息中获取应用名
4:ribbon根据应用名从eureka注册中心获取服务列表
5:ribbon从服务列表中通过相关均衡策略获取具体某个服务
6:请求远程服务

简单来说,就是我们会在请求来的时候,从一个服务集群根据请求挑选一个服务。

②我们要在注册中心已经搭建的基础上,也是就是我们上面所说的服务端(注册中心)搭建的基础上。创建空工程eureka-client-a,这个工程也可以在上面客户端的基础上去使用,也可以新建。

③首先引入依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>


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

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
注意我们这里引入了:ribbon的依赖

④在resources包下创建application.yml文件

spring.application.name: lq-ribbon-a


eureka:
  server:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
server:
  port: 6999

这里的端口号我们命名为:6999

⑤在java包下创建一个包并建立一个启动类ApplicationA

@SpringBootApplication
public class ApplicationA {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }


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

⑥在java包下创建controller包并创建Portcontroller

@RestController
public class PortController {

    @Autowired
    RestTemplate restTemplate;

    @GetMapping("/a")
    public Map<String,Object> b(){
        String url = "http://lq-ribbon-b/b";
        Map<String,Object> result =restTemplate.getForObject(url,Map.class);
        return result;
    }
}

这里我们简单看一下这个方法,当我们通过http://localhost:6999/a时,我们会调用这个方法,这个方法通过http://lq-ribbon-b/b去调用微服务。

⑦新建两个空的工程,分别命名为eureka-client-b1和eureka-client-b2
⑧在两个工程中分别引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>


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

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

⑨在两个工程中的resources包下创建application.yml文件
注意,在spring.application.name的名字需要相同,相同是为让我们A服务调用的时候可以进行。。。。。。。。。。。。,而端口号一个为7001,一个为7002

spring.application.name: lq-ribbon-b


eureka:
  server:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
server:
  port: 7001
spring.application.name: lq-ribbon-b


eureka:
  server:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
server:
  port: 7002

⑤在两个工程下的java包下创建一个包并建立一个启动类ApplicationB

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

⑥在两个工程下的java包下创建controller包并创建Portcontroller
我们在这个方法中,使用了一个随机数,传给了前面,同时我们为了知道我们调用了哪个服务B,所以我们也将我端口号返回。

@RestController
public class PortController {

    @Value("${server.port}")
    Integer port;


    @RequestMapping("/b")
    public Map<String, Object> b(){
        Map<String,Object> result =new HashMap<>();
        result.put("data",System.currentTimeMillis());
        result.put("port", port);
        return result;
    }
}

当我们创建完之后,我们就可以启动项目,同样的这里建议先启动注册中心,其他三个服务顺序无所谓。
启动完成后 进入http://localhost:8761/
我们就可以看到这个样子
在这里插入图片描述

这时候我们去之前说的http://localhost:6999/a 当我们刷新的时候我们就能看到端口是我们之前新建两个服务B的端口号,如果不觉得麻烦的,可以建三个工程试试看。
在这里插入图片描述
在这里插入图片描述
如果做到这一步,我们的负载均衡简单的实践就已经做完了。

接下来我们学习断路器的实践。

三、断路器的搭建
我们首先要明白断路器是的原理是什么,它的目的是什么。简单的举个例子,断路器就好比是我们家里的保险丝,当我们自己家里用的电器功率过大,会断电,但是我们又不能影响整个小区的电,所以就需要保险丝来保护其他家的电不被断掉。断路器的原理也是同样的,当我们的整个项目,如果有一个服务down掉了,我们不应该是整个服务都不可以用了,而只能是这个功能使用而已。
简单了解之后,我们现在开始搭建项目

①首先,我们建立一个空的子工程(我这里所有的子工程都是在我第一次建立的父工程基础上)

②引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

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

③在java包下创建hystrix包,并创建启动类HystrixApplication

@SpringBootApplication
@EnableCircuitBreaker
public class HystrixApplication {

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

④并在hystrix包下创建Controller包

⑤在controller下建类HystrixController

@RestController
public class HystrixController {

    @GetMapping("/hello")
    @HystrixCommand(fallbackMethod = "hystrixHello")//这个注解是当这个方法发生异常
//执行“hystrixHello”的
    public String hello(){
        //throw new RuntimeException("xxx");//为了方便,我们直接抛出异常。
        return "hello world";//在我们正常执行时,执行此代码
    }

    public String hystrixHello(){
        return "hystrix Hello world";
    }

}

⑥我们启动项目,同样的我们首先要启动服务端,然后再启动我们这个项目,当我们启动好项目,我们可以看到自己启动项目的端口,一般都是8080
所以我们访问:http://localhost:8080/hello

这时当我们选择直接抛出异常的时候,我们可以看到结果:
在这里插入图片描述
而当我们正常执行的时候,我们可以看到结果:
在这里插入图片描述
在这里,我们断路器的简单实战就结束了
接下来,我们需要学习断路器的合并请求实战
因为依赖服务的线程池有限,将出现排队等待与响应延迟的情况,为了优化这些问题,Hystrix提供了HystrixCollapser来实现请求的合并,以减少 通信消耗和线程数的占用。

HystrixCollapser实现了在HystrixCommand之前放置一个合并处理器,将处于一个很短的时间窗(默认10毫秒)内对同一依赖服务的多个请求进行整合并以批量方式发起请求的功能(服务提供方也需要提供相应的批量实现接口)。

看懂了吗?没有吧 反正我也没想通为啥合并请求要放到断路器里,但是实现起来还是挺简单的。

①首先我需要在之前搭建的断路器项目上进行操作。

②在controller下建类CollapserController

@RestController
public class CollapserController {

    @Autowired
    CollapseService collapseService;

    @GetMapping("/list")
    public List<Integer> getStroeList()throws Exception{
        HystrixRequestContext.initializeContext();
        Future<Integer> store1=collapseService.getStore(new Random().nextInt());
        Thread.sleep(100L);
        Future<Integer> store2 =collapseService.getStore(new Random().nextInt());
        List<Integer> result =new ArrayList<>();
        result.add(store1.get());
        result.add(store2.get());
        return result;
    }

}

③并在hystrix包下创建Service包

④在service包里创建collapseService

@Service
public class CollapseService {

    Logger logger = LoggerFactory.getLogger(CollapseService.class);

    // timerDelayInMilliseconds时间延迟
    @HystrixCollapser(batchMethod = "getStoreList", collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds", value = "1000")})
    public Future<Integer> getStore(Integer id){
        return null;
    }

    @HystrixCommand
    public List<Integer> getStoreList(List<Integer> id){
        logger.info("合并请求调用 入参集合数量" +id.size());
        List<Integer> result =new ArrayList<>();
        for (Integer i:id){
            Integer rand =new Random().nextInt();
            logger.info("合并请求调用 输出"+rand);
            result.add(rand);
        }
        return result;
    }
}

当我们启动项目访问http://localhost:8080/list时 会产生两个随机数

在这里插入图片描述
控制台输出
在这里插入图片描述

接下来,我们学习Feign

四、Spring Cloud Feign 声明式服务调用

首先我们需要了解Feign是什么?

官方解释:是一个申明式微服务调用客户端

我们一般调用是通过http的调用方式

其中我们最长采用的是Spring Boot的RestTemplate.
在微服务中,RestTemplate是一个很好的选择。

那为什么我要使用Feign 是因为他是基于注解的。

我在使用Feign时,我们需要把我们服务端和客户端都注册到Eureka,并且我们在客户端需要使用注解@EnableFeignClients和@Feignclient注解

  1. 创建客户端的空工程-feign-client

  2. 导入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>
  1. 在resources下创建application.yml文件
spring.application.name: feign-client


eureka:
  server:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
server:
  port: 9001
  1. 在java目录下创建一个fenign包,并创建启动类FeignClientApp
@SpringBootApplication
public class FeignClientApp {
    public static void main(String[] args) {
        SpringApplication.run(FeignClientApp.class);
    }
}
  1. 创建客户端的controller
@RestController
public class OrederController {

    @RequestMapping("/order")
    public Map<String,Object> order(){
        Map<String,Object> order=new HashMap<>();
        order.put("orderId",System.currentTimeMillis());
        order.put("merchantId",System.currentTimeMillis());
        order.put("note","强破破");
        return order;

    }
}
  1. 创建服务端空的工程—feign-server

  2. 引入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
</dependencies>
  1. 在resources创建application.yml文件
spring.application.name: feign-server
eureka:
  server:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
server:
  port: 9002
  1. 在java下创建包并创建启动类 注意要这里要写一个注解@EnableFeignClients
@SpringBootApplication
@EnableFeignClients
public class FeignApplication {
    public static void main(String[] args) {
        SpringApplication.run(FeignApplication.class);
    }
}
  1. 在启动类同级目录创建controller,并controller下创建MerchantController
@RestController
public class MerchantController {

    @RequestMapping("/Merchant/order")
    public Map<String,Object> order(){
        return null;
    }

}
  1. 这是我们要通过feign调用客户端的orderController,所以这是我们需要创建一个接口,
@FeignClient("feign-client")//这里是客户端的名称
//这个接口会被注入springIOC容器成为-Bean
//这和我们的Mybatis 里面的@Mapper 对应的接口是非常类似的
public interface OrderFeignClient {

    @PostMapping("/order")
     Map<String,Object> order();
}
  1. 修改MerchantController 文件
@RestController
public class MerchantController {

    @Autowired
    OrderFeignClient orderFeignClient;

    @RequestMapping("/Merchant/order")
    public Map<String,Object> order(){
        Map<String ,Object> result= orderFeignClient.order();
        result.put("note2","我来自于 Feign server");
      return result;
    }

}

这时我们先启动注册中心,然后再启动客户端程序,进去http://localhost:9001/order

这时我们看到的结果是:
在这里插入图片描述

然后我启动我们的服务端程序,进去http://localhost:9002/Merchant/order

这时我们看到的结果是:
在这里插入图片描述

我们可以从结果中看到我们已经调用了客户端的程序。

五、Spring cloud Zuul微服务网关

  1. 首先我们需要了解为什么要使用微服务网关
    我们所有的微服务都是在内网,没有安全控制,无状态应用。

  2. 首先我们创建空的项目,并注入依赖

<dependencies>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

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

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    </dependency>
</dependencies>
  1. 在resources包下创建application.yml文件
spring.application.name: lq-zuul

eureka:
  server:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
server:
  port: 9003
  1. 在java包下创建子包并同级创建启动类
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class);
    }
}

当我们搭建项目到现在,我们启动注册中心 并且启动我们之前的lq-eureka-client-a
在lq-eureka-client-a 的controller中写这个方法

@GetMapping("/aa")
public Map<String,Object> aa(){

    Map<String,Object> result =new HashMap<>();
    result.put("aa","aa");
    return result;
}

三个项目启动项目后 我们进入http://localhost:9003/lq-ribbon-a/aa 这个地址是我们zuul端口号和我们的 lq-eureka-client-a 服务名 加上我们的接口名称。

得到的结果:
在这里插入图片描述

六、Spring Cloud Config

  1. 首先我们创建空的工程
  2. 然后引入依赖
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-server</artifactId>
    </dependency>
    <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>
</dependencies>
  1. 创建启动类
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class);
    }
}
  1. 创建application.yml文件
spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git:
          uri: https://github.com/qiangpopo/SpringCloud-Respostive/
          username:
          password:

server:
  port: 8888
  1. 然后启动注册中心,启动本项目。

  2. 启动成功以后http://localhost:8888/config-dev.json或者http://localhost:8888/config-dev.yml
    在这里插入图片描述
    在这里插入图片描述

  3. 接下来我们要创建config-client空的工程

  4. 引入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
</dependencies>
  1. 在resourse包下创建bootstrap.yml文件
spring:
  application:
    name: config-client
  cloud:
    config:
      label: master
      profile: dev
      uri: http://localhost:8888/
server:
  port: 8881

eureka:
  server:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
  1. 这是我们值得注意的一点,我踩到了一个坑,因为是在网上找了好多资料,所以在写完yml文件的时候,在github上应该需要一个 在这里插入图片描述
    文件中的内容: 在这里插入图片描述
  2. 创建启动类
@SpringBootApplication
@RestController
public class ConfigClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigClientApplication.class);

    }

    @Value("${foo}")
    String foo;
    @RequestMapping(value = "/hi")
    public String hi(){
        return foo;
    }
}

我们启动项目
http://localhost:8881/hi

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值