SpringCloud笔记

SpringCloud 框架

一、分布式概念

1、分布式的优点

  • 处理高并发问题:

    ​ 面对越来越大的业务量,一台机器的性能已经无法满足。我们需要多台机器才能满足高并发的需求。因此我 们需要垂直或水平拆分业务系统,让它变成一个分布式的架构。

  • 加强系统可用性:

    ​ 为了我们的系统不会因为一台机器出故障二导致整个系统不可用。所以需要利用分布式来消除单点故障,提 高系统可用性。

  • 把模块拆分,使用接口通信,降低程序的耦合度

  • 把项目拆分成若干个子项目,不同的团队负责不同的子项目

  • 增加项目时只需增加一个子项目,调用其他子项目的接口即可

2、项目的拆分

拆分的原则:

(1)纵向拆分:按层拆分

​ 如:

​ pro1——实体类(各个项目通用)

​ pro2——工具类

​ pro3——dao

​ pro4——service

​ pro5——controller

​ pro6——view

(2)横向拆分——按功能拆分

​ 如:

​ pro1——实体类

​ pro2——登录

​ pro3——注册

​ …

(3)综合拆分

​ 综合拆分是 SpringCloud 的核心思想,本文如下重点介绍:

二、综合拆分

拆分出来的项目(微服务)都有两个角色:

​ 服务提供者(供其他服务调用);

​ 服务消费者(调用其他服务的服务)。

服务治理:服务的启动、负载、运载等(比如运维)。

2.1 SpringCloud六大组件

1、Eureka 注册中心:

​ 职能:提供服务者,把服务注册到Eureka中——包含服务名、IP、port;

​ 服务消费者通过注册中心来发现、获取服务列表——调用获取到的服务。

2、Robbin:负载均衡:

​ 职能:通过负载均衡算法调用服务提供者的服务。

负载均衡策略:轮询、随机、根据响应时间分配权重、选择并发量最小的、根据服务性能和可用性…

3、Feign:服务的通讯

​ 职能:服务消费者通过Feign来调用服务消费者。

4、Hystrix:断路器

​ 职能:通过服务熔断、降级来保护请求,处理压力较大的服务。

5、GateWay:路由

​ 职能:提供路由

6、Config:配置服务

​ 职能:提供统一的优化配置。

三、Eureka注册中心

3.1 概念

Eureka与服务提供方之间是长链接。

【说明】服务消费者不能直接调用服务!

​ 原因:1、耦合度高:服务消费者需要储存服务提供者的IP、port,当服务提供者的IP、port发生变化后, 必须手动更新服务消费者,否则无法调用;

​ 2、服务消费者没有负载均衡的依据;

​ 3、当服务提供者发生变化后,服务消费者需要得知。

工作原理:
在这里插入图片描述

3.2 Eureka集群

3.2.1 介绍

EurekaServer:注册中心

​ 职责:1、接收服务的注册、生成注册列表;

​ 2、到其他注册中心获取注册信息——Eureka集群;

​ 3、推送注册列表给服务消费者。

EurekaClient:Eureka客户端 (@EnableEurekaClient)

​ 包含:服务提供者、服务消费者。

3.2.2 配置过程

1、新建子项目Eureka02

2、复制

3、修改application.yml文件

server:
  port: 8091 #1、修改端口号
eureka:
  instance:
    hostname: localhost #两个eureka的hostname名一致,将自动形成通信,建立集群
  client:
    #2、是否向其他eureka注册————改为true
    register-with-eureka: true
    #3、是否不断的寻找其他eureka————改为true
    fetch-registry: true
    service-url:
      defaultZone: http://root:root@${eureka.instance.hostname}:${server.port}/eureka/
spring:
  application:
    name: EUREKA02
  security:
    user:
      name: root
      password: root

四、RestTemplate接口

【作用】服务消费者调用服务提供者。 第二种方式:Feign组件。

【原理】对HTTP协议进行了封装,采用了Restful的模板。

将对象装换成JSON字符串,一定要用@RequestBody注解。

详见 Resttemplate文档。

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

    //程序启动就将创建出bean
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

4.1 getForObject

返回值为服务提供者的返回值,服务提供者与消费者之间传递格式是JSON,两端自动转JSON。

	//controller中注入RestTemplate
	@Autowired
    private RestTemplate restTemplate;

	@GetMapping("/mv")
    public String mvApplyInfo(Integer id) {
        String str = restTemplate.getForObject("http://APPLYMODEL/model/del?id=" + id, String.class);
        return str;
    }
 	@GetMapping("/getInfo")
    public Map getAllApplyInfo() {
        List<ApplyInfo> list = restTemplate.getForObject("http://APPLYMODEL/model/getAll", List.class);
		//List<ApplyInfo> list = JSON.parseObject(str, List.class);
        Map<String, Object> map = new HashMap<>();
        map.put("mdg", "服务器异常");
        map.put("code", 0);
        map.put("data", list);
        map.put("count", list.size());
        return map;
    }

4.2 postForObject

返回值为服务提供者的返回值,服务提供者与消费者之间传递格式是JSON,两端自动转JSON。

@PostMapping("/ad")
    public String addApplyInfo(ApplyInfo applyInfo) {
        Integer result = restTemplate.postForObject("http://APPLYMODEL/model/add", applyInfo, Integer.class);
        if (1 == result) {
            return "true";
        } else {
            return "error";
        }
    }

五、Ribbon实现负载均衡

配置服务提供者的负载均衡!!!

【注意】1、Ribbin实现的是服务提供者的负载均衡(model层)、服务消费者的负载均衡需要Nginx实现;

​ 2、负载均衡需要配置在服务消费者层,消费者获取到提供者的集群信息,消费者依据负载均衡策略决定 调用哪一个;

​ 3、服务提供者的服务名一定要相同,服务名相同的认为是同一集群。

负载均衡主要原理: 注册中心根据负载均衡算法,给出调用建议,然后消费者根据建议进行调用。

配置步骤:

1、在服务消费者处添加 jar依赖

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

2、服务消费者中指定负载均衡算法,两种方式:

​ (1)创建Bean的方式 —— 服务消费者的启动类中

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

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

    @Bean
    //指定轮训的负载均衡算法————轮询
    public IRule createRule(){
        return new RoundRobinRule();
    }
}

​ (2)配置文件方式(服务消费者的application.xml文件)

APPLYMODEL: #服务消费者的服务名(spring/application/name),此时也可以称作为服务消费者的集群名
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule

六、Security 安全

【作用】给Eureka注册中心设置账号密码,每一个Client(服务提供者、服务消费者)向注册中心注册必须提供账 号密码。

步骤:

1、Eureka服务中添加依赖

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

2、在启动包下创建类,实现WebSecurityConfigurerAdapter接口。

​ 注解方式启用Security

@EnableWebSecurity
public class WebSecurityApp extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().ignoringAntMatchers("/eureka/**");
        super.configure(http);
    }
}

3、在application.xml配置文件中设置账号密码

spring:
  application:
    name: EUREKA01
  security:
    user:
      name: root
      password: root

4、服务提供者、服务消费者必须携代账号密码才能注册到Eureka的注册列表中

eureka:
  client:
    service-url:
      defaultZone: http://root:root@localhost:8090/eureka,http://root:root@localhost:8091/eureka

七、分布式下的CAP定理

C:一致性,调用分布式下的集群,每一个服务提供者返回的结果都是相同的;

A:可用性,服务要高可用,搭建集群;

P:容错性,发生故障后要保证服务正常运行。

选择分析:

(1)在分布式系统中一定要保证P,允许服务出错(宕机、网络不通等),需要C和A之间进行选择;

(2)如果选择C,那么实现容错性P后,还用保持一致性,那么最终结果是服务提供者一致出错;

(3)Eureka集群选择AP,即一个服务出错后,既能保持可用性,又能保证其他程序正常运行。

八、Feign组件的使用

【原理】基于接口,使用代理方式实现服务的调用

【作用】用于服务之间的调用,简化RestTemplate操作

应用在服务消费者端。

8.1 Feign与RestTemplate区别

推荐使用Feign

都是通过对HTTP协议进行的封装

1、RestTemplate通过封装HTTP,直接发送请求,请求服务提供者。每次请求需要提供服务提供者的URL路径;

2、Feign基于接口,动态代理方式发送的请求,调用更方便;

3、RestTemplate只能调用注册到注册中心得服务,外部服务无法直接调用;

​ Feign可以直接通过IP方式调用外部服务;

8.2 Feign使用和相应注解

1、在消费者的pom.xml中添加jar依赖

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

2、在启动程序类上添加注解

@EnableFeignClients

3、编写接口,在接口中编写请求提供服务的方法

【注意】必须用@RequestMapping注解

@FeignClient("APPLYMODEL") //请求提供者名称
public interface ApplyFeigns {

    //1、无参
    @RequestMapping(value = "/model/count", method = RequestMethod.GET)
    Integer getCountApply();

    //2、单个参数
    @RequestMapping(value = "/model/getInfoById/{id}")
    ApplyInfo getApplyInfoById(@PathVariable("id") Integer id);

    //3、多个参数(也可以像单个参数那样用/分开)
    @RequestMapping(value = "/model/more")
    ApplyInfo getMorePar(@RequestParam("applyUserId") Integer applyUserId, @RequestParam("mark") String mark);

    //4、对象参
    @RequestMapping(value = "/model/obj", method = RequestMethod.POST)
    String getByObj(@RequestBody ApplyInfo applyInfo);
}

4、在Controller中注入接口对象,调用

@RestController
@RequestMapping("/apply")
@CrossOrigin("http://127.0.0.1:8848")  //跨域
public class ApplyInfoController {

    @Autowired
    private ApplyFeigns applyFeigns;

    //feign 1   无参
    @GetMapping("/count")
    public String getCount() {
        return "一共" + applyFeigns.getCountApply() + "条记录";
    }

    //feign 2  单个参数
    @GetMapping("/getInfoById")
    public ApplyInfo getApplyInfoById(Integer id) {
        return applyFeigns.getApplyInfoById(id);
    }

    //feign  多个参数
    @GetMapping("/more")
    public ApplyInfo getApplyInfoMoreParam(Integer applyUserId,String mark){
        return applyFeigns.getMorePar(applyUserId, mark);
    }

    //feign  对象参
    @GetMapping("/obj")
    public String getByObj(ApplyInfo applyInfo){
        return applyFeigns.getByObj(applyInfo);
    }

}

8.3 服务降级/熔断

当消费者请求提供者时,服务者可能由于某种问题(网络延迟、宕机)不能进行正常的处理,那么Feign会调用一个“备用”的方法,这个“备用”方法返回出错信息或提示,这种情况也称为服务降级。

步骤

1、实现我们定义的Feign接口,在Feign接口中写服务降级方法。

@Component
public class ApplyFeignFallBack implements ApplyFeigns {
    @Override
    public Integer getCountApply() {
        System.out.println("服务器异常,实行服务降级策略");
        return -1;
    }

    @Override
    public ApplyInfo getApplyInfoById(Integer id) {
        return null;
    }

    @Override
    public ApplyInfo getMorePar(Integer applyUserId, String mark) {
        return null;
    }

    @Override
    public String getByObj(ApplyInfo applyInfo) {
        return null;
    }
}

2、服务消费者的pom.xml中配置开启服务降级——Feign调用熔断器Hystrix实现降级的

server:
  port: 8093
spring:
  application:
    name: APPLY
eureka:
  client:
    service-url:
      defaultZone: http://root:root@localhost:8090/eureka,http://root:root@localhost:8091/eureka
feign:
  hystrix:
    enabled: true  #配置使用服务降级

3、接口中指定fallback为我们的服务降级方法。

@FeignClient(value = "APPLYMODEL", fallback = ApplyFeignFallBack.class)
public interface ApplyFeigns {

    @RequestMapping(value = "/model/count", method = RequestMethod.GET)
    Integer getCountApply();

    @RequestMapping(value = "/model/getInfoById/{id}")
    ApplyInfo getApplyInfoById(@PathVariable("id") Integer id);

    @RequestMapping(value = "/model/more")
    ApplyInfo getMorePar(@RequestParam("applyUserId") Integer applyUserId, @RequestParam("mark") String mark);

    @RequestMapping(value = "/model/obj", method = RequestMethod.POST)
    String getByObj(@RequestBody ApplyInfo applyInfo);
}

4、客户端调用controller,如果发生异常或者响应超时,将服务降级/熔断。

8.3 FeignClientsFallbackFactory

如果需要统一打印引发服务降级的异常信息,那么可以使用FeignClientsFallbackFactory

1、编写FallBackFactory实现类 FeignClientsFallBackFactory.java

​ 泛型为接口

@Component
public class ApplyFeignFallBackFactory implements FallbackFactory<ApplyFeigns> {

    @Autowired
    private ApplyFeignFallBack applyFeignFallBack;

    @Override
    public ApplyFeigns create(Throwable throwable) {
        System.out.println("打印异常信息");
        throwable.printStackTrace();
        return applyFeignFallBack;
    }
}

2、在FeignClients出处指定fallbackFactory

@FeignClient(value = "APPLYMODEL",fallbackFactory = ApplyFeignFallBackFactory.class)
public interface ApplyFeigns {
			......
	}

8.4 Feign实现负载均衡

Feign实现负载均衡需要整合Robbin,详见第五章。

九、Hystrix组件

【说明】8.3 当中,我们使用Feign组件并在yam文件中配置hystrix实现了服务降级功能,但那并不是原生hystrix组件实现的,是feign组件集成了hystrix组件的部分功能实现的,功能有限。这里我们将单独使用hystrix组件,感受他的强大之处。比如实现RestTemplate调用方式下的服务降级/熔断。

9.1 hystrix服务降级

依赖:

		<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
9.1.1 RestTemplate调用方式的服务降级

同类中定义好降级方法,调用即可。

具体步骤:

1、启动类上方添加注解: @EnableHystrix

2、同一个controller类中自定义服务降级方法

3、controller方法上方注解选取自定义降级方法

@RestController
@RequestMapping("/apply")
@CrossOrigin("http://127.0.0.1:8848")
public class ApplyInfoController {
   
    @Autowired
    private RestTemplate restTemplate;
    
    //第3步
    @GetMapping("/getInfo")
    @HystrixCommand(fallbackMethod = "getInfoHystrix") 
    public Map getAllApplyInfo() {
        List<ApplyInfo> list = restTemplate.getForObject("http://APPLYMODEL/model/getAll", List.class);
        Map<String, Object> map = new HashMap<>();
        map.put("mdg", "服务器异常");
        map.put("code", 0);
        map.put("data", list);
        map.put("count", list.size());

        return map;
    }
    
    //第2步
	public Map getInfoHystrix() {
        Map<String, String> map = new HashMap<>();
        map.put("code", "1服务降级");
        return map;
    }
}
9.1.2 Feign使用独立的hystrix

与 9.1.1 方式相同,无需再写接口的实现类。

9.2 hystrix线程隔离

//1、Hystrix线程
    @HystrixCommand(fallbackMethod = "getCount3FallBack", commandProperties = {
           @HystrixProperty(name = "execution.isolation.strategy",value = "THREAD"),
          @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
   })

    //2、信号量---了解
@HystrixCommand(fallbackMethod = "getCount3FallBack", commandProperties = {
        @HystrixProperty(name = "execution.isolation.strategy",value = "SEMAPHORE"),
        @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10")
})

9.3 hystrix断路器

hystrix实现断路器,也是通过fallbackMethod指定断路后的方法实现的,但是与服务降级不同的是:

服务降级——请求会访问到服务提供者,服务提供者方法发生异常,最后走FallbackMethod;

断路器——请求不会访问到服务提供者,一段时间内达到了一定的阈值,将自动调用FallbackMethod。断路器默认每隔五秒进入一次半开状态,放行一个请求如运行成功则关闭断路状态。

在这里插入图片描述

代码:

//2、hystrix整合RestTemplate 实现:服务降级&断路器
    @GetMapping("/getInfo")
    //注意:必须有HystrixCommand注解,Hystrix面板才能监控到
    @HystrixCommand(fallbackMethod = "getInfoHystrix", commandProperties = {
            //线程隔离
            @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000"),
            //断路器
            @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "5000")
    })
    public Map getAllApplyInfo() {
        System.out.println("controller,当前线程基本信息:" + Thread.currentThread().getContextClassLoader());
        List<ApplyInfo> list = restTemplate.getForObject("http://APPLYMODEL/model/getAll", List.class);
        Map<String, Object> map = new HashMap<>();
        map.put("mdg", "服务器异常");
        map.put("code", 0);
        map.put("data", list);
        map.put("count", list.size());
        return map;
    }

    //2、hystrix 整合 RestTemplate 服务降级  ————降级方法
    public Map getInfoHystrix() {
        Map<String, String> map = new HashMap<>();
        map.put("code", "1服务降级");
        return map;
    }

9.4 hystrix控制面板/仪表盘Dashboard

hystrix控制面板实现请求的监测,可以在controller模块中配置启动,也可以单独创建子项目Model来配置。

1、引入依赖

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

2、启动类上添加注解

@EnableHystrixDashboard

3、启动类中配置(创建Bean)

@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;
    }

4、浏览器打开dushboard主页

在这里插入图片描述

5、监控中心

dushboard中输入网址:http://localhost:8093/hystrix.stream 进入监控中心

访问实现服务降级的方法(具有@HystrixCommand(fallbackMethod = “M1”) 注解修饰的方法)将被检测到。

在这里插入图片描述

十、Config配置中心组件

【功能】对子项目中的配置文件实现统一管理

配置步骤:

1、单独创建配置中心的子项目

  • 配置中心项目的pom.xml文件中引入config服务端的依赖
  • 被管理的子项目的pom.xml文件中引入config客户端的依赖
		<!--1、配置中心server端插件-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
		<!--2、配置中心client端插件-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>

2、创建启动类

@EnableConfigServer注解:表名这是一个配置中心的服务端,其他交由我统一管理的都是我的客户端。

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

3、管理中心的resources根目录下创建application.yml配置文件

server:
  port: 8098  #该配置中心的端口号,被管理的子项目通过这个端口号拉取他们的配置文件
spring:
  application:
    name: PROCONFIG
  profiles:
    active: native
  cloud:
    config:
      server:
        native:
          search-locations: classpath:config  #其他交由我管理的配置文件在config文件夹下

4、管理中心的resources文件夹下创建config文件夹,里面放入各个子项目的配置文件

​ 与原来配置文件不同的是:以前所有子项目的配置文件都叫application.yml,现在他们可以自定义一个名字(但是必须有 -local.yml后缀),配置文件内容与之前的一致。如下:
在这里插入图片描述

5、被配置中心统一管理的子项目必须指定拉取配置文件地址、文件名

​ 方法:被管理的子项目resources文件夹下创建bootstrap.yml文件。

spring:
  cloud:
    config:
      uri: http://localhost:8098  #管理中心的端口号
      name: model2 #管理中心的我的配置文件的名称
      profile: local #管理中心的我的配置文件的名称后缀

【说明】application.yml文件与bootstrap.yml文件,都是SpringCloud中规定好的文件名,规范大于代码。

​ 区别:bootstrap.yml文件的加载顺序高于application.yml文件的加载顺序。

十一、GateWay路由

【说明】路由也能实现负载均衡,提供客户端不用经过服务消费者直接调用服务提供者的方式。

1、依赖

	<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-gateway</artifactId>
        </dependency>
        <!--配置中心client端插件-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
    </dependencies>

2、配置文件

server:
  port: 8097
spring:
  application:
    name: GATEWAY
  cloud:
    gateway:
      routes:
        - id: ID001 #配置文件内不能重复即可
          predicates:
            - Path=/model/**  #model为服务提供者controller接口的第一个requestMapping
          uri: lb://APPLYMODEL  #服务提供者名字
eureka:
  client:
    service-url:
      defaultZone: http://root:root@localhost:8090/eureka,http://root:root@localhost:8091/eureka

3、启动类

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

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

2、配置文件

server:
  port: 8097
spring:
  application:
    name: GATEWAY
  cloud:
    gateway:
      routes:
        - id: ID001 #配置文件内不能重复即可
          predicates:
            - Path=/model/**  #model为服务提供者controller接口的第一个requestMapping
          uri: lb://APPLYMODEL  #服务提供者名字
eureka:
  client:
    service-url:
      defaultZone: http://root:root@localhost:8090/eureka,http://root:root@localhost:8091/eureka

3、启动类

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

@SpringBootApplication
@EnableEurekaClient
public class GateWayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GateWayApplication.class, args);
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值