springcloud+dubbo+nacos搭建(nacos使用mysql存储)

前面两篇讲了不用springcloud做粘合,直接springboot+dubbo+nacos+sentinel的搭建

这篇讲用springcloud做粘合是怎么搞的

一、nacos作为注册中心

1.原理

 

2.调用方business服务代码

场景是business项目调用storage项目

目录

pom.xml

<?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>test-spring-cloud-alibaba</artifactId>
        <groupId>com.sid</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>business</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.1.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-dubbo</artifactId>
            <version>2.1.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>2.1.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
            <scope>runtime</scope>
        </dependency>

        <!-- 这个是接口包 -->
        <dependency>
            <groupId>com.sid</groupId>
            <artifactId>api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <!-- 不加这个不行,好像是给nacos上报用的http协议 -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.6</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.0.1.RELEASE</version>
            </plugin>
            <!-- mybatis generator 自动生成代码插件 -->
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.7</version>
                <configuration>
                    <configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile>
                    <overwrite>true</overwrite>
                    <verbose>true</verbose>
                </configuration>
                <dependencies>
                    <!-- 这样就不用再generatorConfig.xml里面配置本地的mysql-connector-java位置了-->
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>5.1.46</version>
                        <scope>runtime</scope>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
</project>

bootstrap.yml

spring:
  application:
    name: app-business
  cloud:
    nacos:
      # Nacos 服务发现与注册配置
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848

 application.yml

server:
  port: 8085


###起个名字作为服务名称(该服务注册到nacos注册中心的名称,比如订单服务)
spring:
  application:
    name: app-business
  datasource:
    url: jdbc:mysql://localhost:3306/test-business
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver

# Dubbo Application
dubbo:
  # Dubbo Protocol
  protocol:
    name: dubbo
    ## Random port
    port: -1
  ## Dubbo Registry
  registry:
    address: spring-cloud://localhost
  application:
    name: app-business
  scan:
    #这里是表明RPC接口包的package
    base-packages: com.sid.rpc.service
  cloud:
    #business这个服务需要调用order和storage服务,这个配置表示去订阅他们
    subscribed-services: app-order,app-storage
## 该配置节点为独立的节点,不是在在spring的节点下
mybatis:
  mapper-locations: classpath:mapping/*.xml  #注意:一定要对应mapper映射xml文件的所在路径
  type-aliases-package: com.sid.model  # 注意:对应实体类的路径
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #控制台打印sql

# log config
logging:
  config: classpath:logback-spring.xml

启动类

package com.sid;

import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


@SpringBootApplication
@EnableDubbo
@MapperScan("com.sid.mapper")
public class BusinessApp {
    public static void main(String[] args) {
        SpringApplication.run(BusinessApp.class, args);
    }

}

 BusinessServiceImpl.java

该类里面就是用dubbo去RPC调用storage服务和order服务

package com.sid.service;

import com.sid.mapper.BusinessMapper;
import com.sid.model.Business;
import com.sid.rpc.service.model.Order;
import com.sid.rpc.service.service.OrderServiceApi;
import com.sid.rpc.service.service.StorageServiceApi;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.List;

@Service
public class BusinessServiceImpl implements BusinessService{

    @Reference(check = false,group = "order-provider", version = "1.0.0")
    OrderServiceApi orderServiceApi;

    @Reference(check = false,group = "storage-provider", version = "1.0.0")
    StorageServiceApi storageServiceApi;

    @Resource
    BusinessMapper businessMapper;

    /**
     * 采购
     */
    @Transactional
    @Override
    public void purchase(String userId, String commodityCode, int orderCount) {

        Business b = new Business();
        b.setUserId(userId);
        b.setCommodityCode(commodityCode);
        b.setOrderCount(orderCount);

        int i = businessMapper.insertSelective(b);

        String storageDeductResult = storageServiceApi.deduct(commodityCode, orderCount);
        System.out.println(storageDeductResult);

        Order order = orderServiceApi.create(userId, commodityCode, orderCount);

        String orderCreateResult = order.getMsg();


        if(!storageDeductResult.equals("success") || !orderCreateResult.equals("success")){
            System.out.println("resutl storage service :"+storageDeductResult);

            System.out.println("resutl order service :"+orderCreateResult);

            throw new RuntimeException("BusinessServiceImpl purchase fail");
        }

    }

}

2.被调用方storage服务代码

目录

pom.xml

<?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>test-spring-cloud-alibaba</artifactId>
        <groupId>com.sid</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>storage</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.1.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-dubbo</artifactId>
            <version>2.1.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>2.1.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <version>2.1.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.sid</groupId>
            <artifactId>api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.6</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.0.1.RELEASE</version>
            </plugin>
            <!-- mybatis generator 自动生成代码插件 -->
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.7</version>
                <configuration>
                    <configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile>
                    <overwrite>true</overwrite>
                    <verbose>true</verbose>
                </configuration>
                <dependencies>
                    <!-- 这样就不用再generatorConfig.xml里面配置本地的mysql-connector-java位置了-->
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>5.1.46</version>
                        <scope>runtime</scope>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
</project>

bootstrap.yml

spring:
  application:
    name: app-storage
  cloud:
    nacos:
      # Nacos 服务发现与注册配置
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848

application.yml

server:
  port: 8083

###起个名字作为服务名称(该服务注册到nacos注册中心的名称,比如订单服务)
spring:
  application:
    name: app-storage
  datasource:
    url: jdbc:mysql://localhost:3306/test-storage
    username: root
    password: root
    # 使用druid数据源
    driver-class-name: com.mysql.jdbc.Driver
  cloud:
    nacos:
      # Nacos 服务发现与注册配置
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848

# Dubbo Application
dubbo:
  # Dubbo Protocol
  protocol:
    name: dubbo
    ## Random port
    port: -1
  ## Dubbo Registry
  registry:
    # 挂载到 Spring Cloud 注册中心
    address: spring-cloud://localhost
  application:
    name: app-storage
  scan:
    base-packages: com.sid.rpc.service

## 该配置节点为独立的节点,不是在在spring的节点下
mybatis:
  mapper-locations: classpath:mapping/*.xml  #注意:一定要对应mapper映射xml文件的所在路径
  type-aliases-package: com.sid.model  # 注意:对应实体类的路径
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #控制台打印sql

启动类

package com.sid;

import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.sid.mapper")
@EnableDubbo
public class StorageApp {
    public static void main(String[] args) {
        SpringApplication.run(StorageApp.class, args);
    }
}

rpc实现类

package com.sid.rpc.service;


import com.sid.rpc.service.service.StorageServiceApi;
import com.sid.service.StorageService;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Autowired;

@Service(group = "storage-provider", version = "1.0.0")
public class StorageServiceRpcImpl implements StorageServiceApi {

    @Autowired
    private com.sid.service.StorageService StorageService;

    /**
     * 商品扣库存
     * */
    @Override
    public String deduct(String commodityCode, int count) {

        return StorageService.deduct(commodityCode,count);
    }
}

业务service实现类

package com.sid.service;

import com.sid.mapper.StorageMapper;
import com.sid.model.Storage;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

@Service
public class StorageServiceImpl implements StorageService {

    @Resource
    private StorageMapper storageMapper;

    /**
     * 商品扣库存
     * */
    @Override
    @Transactional
    public String deduct(String commodityCode, int count) {
        Storage storage = storageMapper.selectByPrimaryKey(commodityCode);
        Integer storageCount = storage.getStorageCount();
        storage.setStorageCount(storageCount - count);
        int i = storageMapper.updateByPrimaryKey(storage);
        //int ii = 1/0;
        if(i == 1){
            return "success";
        }
        return "fail";
    }
}

3 RPC接口包服务代码

StorageServiceApi.java

package com.sid.rpc.service.service;

public interface StorageServiceApi {

    String deduct(String commodityCode, int count);
}

4 启动nacos,启动后这两个服务

http://localhost:8848/

二、nacos作为配置中心

1.原理 

1.Nacos Config 数据结构

Nacos Config 主要通过 dataId 和 group 来唯一确定一条配置.

Nacos Client 从 Nacos Server 端获取数据时,调用的是此接口 ConfigService.getConfig(String dataId, String group, long timeoutMs)。

2.Spring Cloud 应用获取数据

dataId

在 Nacos Config Starter 中,dataId 的拼接格式如下

${prefix} - ${spring.profiles.active} . ${file-extension}

prefix 默认为 spring.application.name 的值,也可以通过springboot的application.yml中的配置项 spring.cloud.nacos.config.prefix来配置。

spring.profiles.active 即为当前环境对应的 profile

注意,当 spring.profiles.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成

${prefix}.{file-extension}

file-extension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension来配置。 默认是 properties 类型。

group

group 默认为 DEFAULT_GROUP,可以通过 spring.cloud.nacos.config.group 配置。

默认情况下,会加载命名空间=public,DataID=${spring.application.name}.properties,Group=DEFAULT_GROUP的配置。

3.自动注入

Nacos Config Starter 实现了 org.springframework.cloud.bootstrap.config.PropertySourceLocator接口,并将优先级设置成了最高。

在 Spring Cloud 应用启动阶段,会主动从 Nacos Server 端获取对应的数据,并将获取到的数据转换成 PropertySource 且注入到 Environment 的 PropertySources 属性中,

所以使用 @Value 注解也能直接获取 Nacos Server 端配置的内容。

2.读nacos命名空间public里面的配置

默认情况下,会加载命名空间=public,DataID=${spring.application.name}.properties,Group=DEFAULT_GROUP的配置。

作为配置中心举例就只以business项目举例了

business项目在前面的代码基础上,加入下面的改动

pom.xml加入

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <version>2.1.1.RELEASE</version>
        </dependency>

bootstrap.yml加入

获取配置中心的配置

这里可以看到,直接用spring的@Value就能读到nacos配置中心的配置了(前提是用springcloud来粘合的)

Nacos Config Starter 实现了 org.springframework.cloud.bootstrap.config.PropertySourceLocator接口,并将优先级设置成了最高。

在Spring Cloud应用启动阶段,会主动从Nacos Server端获取对应的数据,并将获取到的数据转换成PropertySource且注入到Environment的PropertySources属性中,所以使用@Value注解也能直接获取Nacos Server端配置的内容。

package com.sid.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

@Configuration
public class NacosConfig {

    @Value("${business.testConfig}")
    private String businessTestConfig;

    @Value("${ip}")
    private String ip;
    
    public String getBusinessTestConfig() {
        return businessTestConfig;
    }

    public void setBusinessTestConfig(String businessTestConfig) {
        this.businessTestConfig = businessTestConfig;
    }

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

}

 这样子搞,是读的nacos的命名空间public里面的东西

默认情况下,会加载命名空间=public,DataID=${spring.application.name}.properties,Group=DEFAULT_GROUP的配置。

在配置列表public命名空间里面创建1个文件,文件的名字是app-business,这个文件的名字要跟该应用的名字一样,文件格式是properties

app-business内容是

启动的时候从日志就能看到本应用读的是nacos配置中的哪个文件,文件里面有些什么内容

写个controller来请求验证一下读到的配置文件的内容

package com.sid.controller;

//import com.sid.config.NacosConfig;

import com.sid.config.NacosConfig;
import com.sid.model.Business;
import com.sid.service.BusinessService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class BusinessController {

    @Autowired
    BusinessService businessService;

    @Autowired
    NacosConfig nacosConfig;

    /**
     * 采购
     */
    @RequestMapping(value = "business/purchase",method = RequestMethod.POST)
    public void purchase(String userId, String commodityCode, int orderCount) {
        businessService.purchase(userId,commodityCode,orderCount);
    }

    @RequestMapping(value = "business/config",method = RequestMethod.GET)
    public NacosConfig config() {
        return nacosConfig;
    }
}

3.nacos配置中心以命名空间区分开发、测试环境不同的配置

在nacos的界面创建2个命名空间,一个是dev,

在配置列表中,dev命名空间下,创建配置文件app-business 

内容如下

代码

主要是修改bootstrap.yml的配置

加上namespace,注意这里的命名空间不是用名字,不是用dev,而是用它的ID,命名空间的ID

启动日志其实就能看到读到了dev命名空间里面的app-business配置文件中的东西了

4.多个服务共用一个公共的配置文件

在nacos的管理界面加入配置文件application.properties,注意要把后缀.properties写全

内容

主要是修改bootstrap.yml的配置,加一个配置,注意要把后缀.properties写全

启动日志

可以看到去加载了文件application.properties

2021-05-27 16:55:40.361  WARN --- [main] c.a.c.n.c.NacosPropertySourceBuilder     Ignore the empty nacos configuration and get it based on dataId[application.properties] & group[DEFAULT_GROUP]
2021-05-27 16:55:40.369  INFO --- [main] c.a.c.n.c.NacosPropertySourceBuilder     Loading nacos data, dataId: 'app-business', group: 'DEFAULT_GROUP', data: business.testConfig=dev
ip=12345678
2021-05-27 16:55:40.373  WARN --- [main] c.a.c.n.c.NacosPropertySourceBuilder     Ignore the empty nacos configuration and get it based on dataId[app-business.properties] & group[DEFAULT_GROUP]
2021-05-27 16:55:40.373  INFO --- [main] b.c.PropertySourceBootstrapConfiguration Located property source: CompositePropertySource {name='NACOS', propertySources=[NacosPropertySource {name='app-business.properties'}, NacosPropertySource {name='app-business'}, NacosPropertySource {name='application.properties'}]}

5.动态刷新配置

Nacos Config Starter 默认为所有获取数据成功的 Nacos 的配置项添加了监听功能,在监听到服务端配置发生变化时会实时触发 org.springframework.cloud.context.refresh.ContextRefresher 的 refresh 方法 。

如果需要对 Bean 进行动态刷新,给类添加 @RefreshScope 或 @ConfigurationProperties注解。

6.Endpoint 信息查看(结合spring-boot-starter-actuator)

Springboot支持这一点,Nacos Config也同时可以使用Endpoint来暴露信息。

在maven 中添加 spring-boot-starter-actuator依赖,并在配置中允许 Endpoints 的访问。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
            <version>2.1.1.RELEASE</version>
        </dependency>

Spring Boot 1.x 中添加配置 management.security.enabled=false

Spring Boot 2.x 中添加配置 management.endpoints.web.exposure.include=*

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

Spring Boot 1.x 可以通过访问 http://127.0.0.1:8085/nacos_config 来查看 Nacos Endpoint 的信息。

Spring Boot 2.x 可以通过访问 http://localhost:8085/actuator/nacos-config 来访问。

在idea中能看到一共有哪些mapping

服务发现的

http://127.0.0.1:8085/actuator/nacos-discovery

三、nacosAPI及客户端的配置相关配置

配置 

nacos作为配置中心,客户端的配置见源码中的NacosConfigProperties类

nacos作为注册中心,客户端的配置见源码中的NacosDiscoveryProperties类

openApi

https://nacos.io/zh-cn/docs/open-api.html

比如查询服务列表

http://localhost:8848/nacos/v1/ns/service/list?pageNo=1&pageSize=100

获取配置

http://localhost:8848/nacos/v1/cs/configs?dataId=app-business&group=DEFAULT_GROUP

发布配置

 

curl -X POST 'http://127.0.0.1:8848/nacos/v1/cs/configs' -d 'dataId=nacos.example&group=com.alibaba.nacos&content=contentTest'  

四、nacos用mysql作为存储

1.初始化数据库

Nacos的数据库脚本文件在我们下载Nacos-server时的压缩包中就有。

进入nacos-server-1.4.1\nacos\conf目录,里面有个初始化文件:nacos-mysql.sql。

自己在mysql中创建一个nacos 的数据库,然后执行nacos-mysql.sql初始化脚本,成功后会生成一些表。

2.修改配置文件

这里是需要修改Nacos-server的配置文件

Nacos-server其实就是一个Java工程或者说是一个Springboot项目,他的配置文件在nacos-server-1.4.1\nacos\conf目录下,名为 application.properties

把配置文件中关于mysql配置的部分打开,本来是注释了的,填入自己的mysql的IP账号密码:

#*************** Config Module Related Configurations ***************#
### If use MySQL as datasource:
spring.datasource.platform=mysql

### Count of DB:
db.num=1

### Connect URL of DB:
db.url.0=jdbc:mysql://localhost:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=root

### Connection pool configuration: hikariCP
db.pool.config.connectionTimeout=30000
db.pool.config.validationTimeout=10000
db.pool.config.maximumPoolSize=20
db.pool.config.minimumIdle=2

3.启动Nacos

先启动Nacos-server,在目录nacos-server-1.4.1\nacos\bin

cmd startup.cmd -m standalone

启动成功后进入Nacos控制台,此时的Nacos控制台中焕然一新,之前的数据都不见了

因为加入了新的数据源,Nacos从mysql中读取所有的配置文件,而我们刚刚初始化的数据库是干干净净的,自然不会有什么数据和信息显示。

4.验证是否持久化到数据库中

添加一些配置

观察数据库nacos中的数据库表 config_info , 如下

 

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值