SpringCloud基础01

1.MicroService(微服务)架构

1.1.什么是MicroService架构

微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟通(通常是基于HTTP的RESTful API)。每个服务都围绕着具体业务进行构建,并且能够被独立地部署到生产环境、类生产环境等。另外,应尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建。(摘自百度)
微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被技术选型,独立开发,独立部署,独立运维,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有情况下,每个任务代表着一个小的业务能力。(摘自百度)
在这里插入图片描述
在这里插入图片描述

1.2.微服务远程调用方式

1.2.1常用调用方式:

  • RPC:Remote Produce Call远程过程调用,类似的还有RMI。自定义数据格式,基于原生TCP通信,速度快,效率高。早期的webservice,现在热门的dubbo,都是RPC的典型
  • Http:http其实是一种网络传输协议,基于TCP,规定了数据传输的格式。现在客户端浏览器与服务端通信基本都是采用Http协议。也可以用来进行远程服务调用。缺点是消息封装臃肿。
    现在热门的Rest风格,就可以通过http协议来实现

1.2.2.如何选择:

  • 速度来看,RPC要比http更快,虽然底层都是TCP,但是http协议的信息往往比较臃肿,不过可以采用gzip压缩。
  • 难度来看,RPC实现较为复杂,http相对比较简单
  • 灵活性来看,http更胜一筹,因为它不关心实现细节,跨平台、跨语言。
  • 如果对效率要求更高,并且开发过程使用统一的技术栈,那么用RPC还是不错的。 dubbo
  • 如果需要更加灵活,跨语言、跨平台,显然http更合适 springcloud

那么我们该怎么选择呢?
微服务,更加强调的是独立、自治、灵活。而RPC方式的限制较多,因此微服务框架中,一般都会采用基于Http的Rest风格服务。

1.3实现技术解决方案

1.3.1.SpringBoot解决方案:

Java中可以使用传统ssm ssj等架构,当然更加推荐Springboot。Spring Boot是一套快速配置脚手架,可以基于Spring Boot快速开发单个微服务;
具体SpringBoot的教程,可以参考我的另外三个博客:
SpringBoot基础01
SpringBoot基础02
SpringBoot基础03
这里不做过多赘述。

1.3.2.服务治理框架:

由于微服务架构中存在多个微服务,那么如何管理和协调这些服务呢?就需要服务治理框架,而springcloud就是一个基于Spring Boot实现的服务治理工具包

2.SpringCloud

2.1是什么:

Spring cloud是一个基于Spring Boot实现的服务治理工具包,用于微服务架构中管理和协调服务的。

2.2组成部分:

Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Springcloud并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
主要组成成分为:
在这里插入图片描述
而五大神兽则是SpringCloud的灵魂
五大神兽:
服务注册发现——Netflix Eureka : 注册所有微服务的通信地址
客服端负载均衡——Netflix Ribbon\Feign :服务之间的调用问题
断路器——Netflix Hystrix :解决微服务故障问题,微服务的隔离
服务网关——Netflix Zuul :微服务的统一入口
分布式配置——Spring Cloud Config :统一管理微服务的配置文件

但SpringCloud的工具包远不止这5个,这只是列举了一部分,具体的可查阅SpringCloud官网。

2.3搭建一个SpringCloud项目:

由于我使用的是Idea,所以这里首先创建一个普通Maven项目,将它命名为SpringCloud-parent作为父路径
在这里插入图片描述
随即删除该目录的Src目录,因为这里作为父目录,所以用不到Src。
之后导入父目录的pom

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.penny</groupId>
    <artifactId>day72-springcloud-parent</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>product-interface</module>
        <module>product-service-8081</module>
        <module>eureka-server-7081</module>
        <module>order-service-9081</module>
        <module>eureka-server-7082</module>
    </modules>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.SR1</spring-cloud.version>
        <springboot.version>2.0.5.RELEASE</springboot.version>
    </properties>

    <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>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${springboot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


</project>

里面分别导入了SpringCloud和SpringBoot的版本控制依赖以及配置了编码格式和定义了Java版本。

之后便右键我们的Springcloud-parent目录,创建一个Module模块,叫做Product-interface用来放我们的公共代码(P.s:此处仅存放公共代码,并非一个接口)
product-inteface:
在这里插入图片描述
pom如下:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>day72-springcloud-parent</artifactId>
        <groupId>com.penny</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>product-interface</artifactId>


</project>

创建一个测试用的Product实例:
Product:

package com.penny.domain;

/*提供公共的domain,query,依赖等*/
public class Product {
    private Long id;
    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Product{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }

    public Product(Long id, String name) {
        this.id = id;
        this.name = name;
    }

    public Product() {
    }
}

公共代码模块创建完成后,根据SpringCloud微服务原理,需要一个服务提供者,一个服务消费者才能让该模式正常运作,所以接下来我们创建一个服务提供者,命名为Product-service-8081(P.s:8081为服务端口号)
Product-service-8081:
项目结构如下:
在这里插入图片描述
pom如下:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>day72-springcloud-parent</artifactId>
        <groupId>com.penny</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>product-service-8081</artifactId>

    <dependencies>

        <!--公共依赖-->
        <dependency>
            <groupId>com.penny</groupId>
            <version>1.0-SNAPSHOT</version>
            <artifactId>product-interface</artifactId>
        </dependency>
        <!--springboot支持-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

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

    </dependencies>


</project>

公共依赖的product-interface千万不要忘了加!!!

随后我们来创建服务启动类 ProductService8081App:
ProductService8081App:

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

然后我们来创建一个测试controller
ProductController:

@RestController
@RequestMapping("/product")
public class ProductController {
    //restfull风格
    @GetMapping("{id}")
    public Product getProductById(@PathVariable(name = "id")Long id){
        return new Product(id,"阿莫西林");
    }
}

随后配置一下application.yml
application.yml:

server:
  port: 8081
spring:
  application:
    name: PRODUCT-SERVICE-8081 #服务的名字

然后启动服务后,在浏览器里输入http://localhost:8081/product/1即可查看到结果
在这里插入图片描述

创建完服务提供者过后,接下来我们创建一个服务的消费者order-service-9081
order-service-9081:
右键SpringCloud-parent新建一个模块,命名为order-service-9081
项目结构如下:
在这里插入图片描述
pom如下:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>day72-springcloud-parent</artifactId>
        <groupId>com.penny</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>order-service-9081</artifactId>


    <dependencies>
    <!--公共依赖-->
    <dependency>
        <groupId>com.penny</groupId>
        <version>1.0-SNAPSHOT</version>
        <artifactId>product-interface</artifactId>
    </dependency>

    <!--springboot支持-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>

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


</project>

同样不要忘了加公共依赖的product-interface!!!

随后一个启动类:
OrderService9081App:

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

然后创建一个配置类CfgBean,该配置类主要通过RestTemplate远程调用服务提供者的服务,
CfgBean:

package com.penny.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class CfgBean {

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

然后创建一个测试Controller
OrderController:

package com.penny.controller;

import com.penny.domain.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController
@RequestMapping("/order")
public class OrderController {
    @Autowired
    private RestTemplate restTemplate;
    //通过暴露id远程查询product的接口
    @Autowired
    private DiscoveryClient discoveryClient;
    @GetMapping("/product/{id}")
    public Product getProductById(@PathVariable("id") Long id){
        //此处获取服务名要小写
        List<ServiceInstance> instances = discoveryClient.getInstances("product-service-8081");
        //此时实例只有一个,正式开发时不这样使用
        ServiceInstance serviceInstance = instances.get(0);
        //serviceInstance.getHost()获取地址
        //serviceInstance.getPort()获取端口
        String Url = "http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+
                "/product/";
        //进行远程调用
        String url = Url+id;
        return restTemplate.getForObject(url, Product.class);
    }
}

然后再配置application.yml
application.yml:

server:
  port: 9081
spring:
  application:
    name: ORDER-SERVICE-9081 #服务名字

随后启动服务,在浏览器里访问http://localhost:9081/order/product/1即可看到效果
在这里插入图片描述
至此,一个最基础的微服务便完成。

2.4.Eureka注册中心

2.4.1.是什么:

Eureka是netflix的一个子模块,也是核心模块之一,Eureka是一个基于REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移。服务注册与发现对于微服务架构来说是非常重要的,有了服务发现和注册,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务,而不需要修改服务调用的配置文件了,功能类似于dubbo的注册中心,比如zookeeper。

2.4.2.原理:

在这里插入图片描述
Eureka采用了C-S的设计架构。Eureka Server作为服务注册功能的服务器,它是服务注册时中心。
而系统中的其他微服务,使用eureka的客户端连接到eureka server并维持心跳连接。这样系统的维护人员就可以通过eureka server来监控系统中各个微服务是否正常运行。SpringCloud的一些其他模块就可以通过eureka server来发现系统中的其他微服务,并执行相关的逻辑。
三大角色:
Eureka server提供服务注册和发现
Eureka Server提供服务注册服务。各个服务提供者节点启动后,会在Eureka Server中进行注册,这样Eureka server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到
Service Provider服务提供方将自身服务注册到Eureka,从而使服务消费方能够找到。(Client)
Service Consumer服务消费方从Eureka获取注册服务列表,从而能够消费服务。(Client)
Eureka client是一个java客户端,用于简化eureka server的交互,在应用启动后,将会向Eureka Server发送心跳。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表把这个服务节点移除。

在这里插入图片描述

2.4.3.注册中心解决的问题:

  • 服务管理
    • 如何自动注册和发现
    • 如何实现状态监管
    • 如何实现动态路由
  • 服务如何实现负载均衡
  • 服务如何解决容灾问题
  • 服务如何实现统一配置

2.4.5注册中心的搭建:

紧接着之前我们创建服务提供者,服务消费者的基础上,右键SpringCloud-parent创建一个模块,命名为:eureka-server-7081
eureka-server-7081:
项目结构如下:
在这里插入图片描述
pom如下:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>day72-springcloud-parent</artifactId>
        <groupId>com.penny</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>eureka-server-7081</artifactId>

    <dependencies>
        <!--springboot支持-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

        <!--Eureka服务端支持-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>


</project>

此处需要注意的是,加了eureka服务端的支持包

<!--Eureka服务端支持-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>

随后创建一个服务启动类EurekaServer7081App
EurekaServer7081App:

package com.penny;

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

@SpringBootApplication
//标志为Eureka服务端
@EnableEurekaServer
public class EurekaServer7081App {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServer7081App.class, args);
    }
}

并且一定要加上@EnableEurekaServer该注解,否则它将无法作为一个注册中心服务端启动
完成之后,来配置application.yml
application.yml:

server:
  port: 7081
eureka:
  instance:
    hostname: eurekaServer-7081 #localhost #eurekaServer-7081 #集群名字
  client:
    registerWithEureka: false #是否要注册到eureka
    fetchRegistry: false #表示是否从Eureka Server获取注册信息
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #访问路径

需要注意的是,此处像我一样的hostname-eurekaServer-7081需要在C:\Windows\System32\drivers\etc\的host文件里映射本地地址才可使用
在这里插入图片描述
如不想映射,可直接填127.0.0.1或者localhost即可

随后启动服务,在浏览器里访问http://localhost:7081,出现如下界面则证明注册中心配置成功
在这里插入图片描述
需要注意的是,如果出现
在这里插入图片描述
这是属于eureka的自我保护机制导致,不影响使用

2.4.6.将服务加入注册中心:

步骤进入到此时,注册中心已经搭建好,但要想在注册中心看到我们之前创建的服务,则需要更改一下配置。
在之前创建服务提供者和服务消费者的时候,已经导入了一个叫做spring-cloud-starter-netflix-eureka-client的依赖包,在注册中心中,服务提供者和服务消费者统一都叫做客户端,注册中心叫做服务端,所以只需在product-service-8081和order-service-9081的pom中导入该依赖即可

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

然后修改product-service-8081的application.yml配置

server:
  port: 8081
spring:
  application:
    name: PRODUCT-SERVICE-8081 #服务的名字
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7081/eureka/ 
  instance:
    prefer-ip-address: true #当调用getHostname获取实例的hostname时,返回ip而不是host名称
    ip-address: 127.0.0.1

然后修改order-service-9081的application.yml配置

server:
  port: 9081
spring:
  application:
    name: ORDER-SERVICE-9081 #服务名字
eureka:
  client:
    service-url:

      defaultZone: http://localhost:7081/eureka/ #指向注册中心
  instance:
    prefer-ip-address: true #当调用getHostname获取实例的hostname时,返回ip而不是host名称
    ip-address: 127.0.0.1

然后在ProductService8081App和OrderService9081App的启动类上加上@EnableEurekaClient该注解,表示开启Eureka客户端

@SpringBootApplication
//开启Eureka客户端
@EnableEurekaClient
public class ProductService8081App {
    public static void main(String[] args) {
        SpringApplication.run(ProductService8081App.class, args);
    }
}
@SpringBootApplication
@EnableEurekaClient
public class OrderService9081App {
    public static void main(String[] args) {
        SpringApplication.run(OrderService9081App.class, args);
    }
}

然后依次启动EurekaServer7081App,ProductService8081App,OrderService9081App即可
在浏览器里我们就能看到两个服务被注册了进来
在这里插入图片描述

2.4.7.Eureka集群

2.4.7.1.为什么需要集群:

设想如果我们的一台注册中心服务器挂掉了,那该注册中心的所有服务也就一并全部挂掉。为了避免出现这种情况,便出现了集群。在原来一个注册中心的基础上,再加1个或N个注册中心服务器,当其中一台挂掉以后,剩余的其他注册中心服务器继续来完成挂掉的那台的工作,从而不影响注册中心里的内容工作。

2.4.7.2.实现:

要想做集群,我们需要在复制一份eureka-server-7081,然后将它命名为:eureka-server-7082,该模块里内容除了application.yml以外,其余内容都一模一样。
接下来便修改eureka-server-7081的application.yml

server:
  port: 7081
eureka:
  instance:
    hostname: eurekaServer-7081 #localhost #eurekaServer-7081 #集群名字
  client:
    registerWithEureka: false #是否要注册到eureka
    fetchRegistry: false #表示是否从Eureka Server获取注册信息
    serviceUrl:
      #http://${eureka.instance.hostname}:${server.port}/eureka/ #单机配置
      defaultZone: http://eurekaServer-7082:7082/eureka/ #集群配置,此处填对方eureka服务地址

在 defaultZone里配置对方注册中心服务器的地址

然后修改eureka-server-7082的application.yml

server:
  port: 7082
eureka:
  instance:
    hostname: eurekaServer-7082 #localhost #eurekaServer-7081 #集群名字
  client:
    registerWithEureka: false #是否要注册到eureka
    fetchRegistry: false #表示是否从Eureka Server获取注册信息
    serviceUrl:
      #http://${eureka.instance.hostname}:${server.port}/eureka/ #单机配置
      defaultZone: http://eurekaServer-7081:7081/eureka/ #集群配置,此处填对方eureka服务地址

与eurekaServer-7081同理,配置对方注册中心服务器的地址

然后修改product-service-8081的application.yml

server:
  port: 8081
spring:
  application:
    name: PRODUCT-SERVICE-8081 #服务的名字
eureka:
  client:
    service-url:
      #http://localhost:7081/eureka/ #单机配置
      defaultZone: http://eurekaServer-7081:7081/eureka/,http://eurekaServer-7082:7082/eureka/ #集群配置
  instance:
    prefer-ip-address: true #当调用getHostname获取实例的hostname时,返回ip而不是host名称
    ip-address: 127.0.0.1

在defaultZone处填上所有注册中心服务器的地址,地址与地址之间用","逗号隔开
同理配置order-service-9081的application.yml

server:
  port: 9081
spring:
  application:
    name: ORDER-SERVICE-9081 #服务名字
eureka:
  client:
    service-url:
      #http://localhost:7081/eureka/ #单机配置
      defaultZone: http://eurekaServer-7081:7081/eureka/,http://eurekaServer-7082:7082/eureka/ #集群配置
  instance:
    prefer-ip-address: true #当调用getHostname获取实例的hostname时,返回ip而不是host名称
    ip-address: 127.0.0.1

然后启动服务,在浏览器里查看到如下即配置集群完毕
在这里插入图片描述
在这里插入图片描述
7081中能看到7082,7082中能看到7081。哪怕其中一台服务器挂掉,也不影响Product-Service-8081和Order-Service-9081的使用。
至此,最基本的集群配置完毕。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值